From 4868bb4f86dee5a06cab32da7e043a338ae1084f Mon Sep 17 00:00:00 2001 From: Ernest Provo Date: Mon, 13 Jul 2026 20:50:59 -0400 Subject: [PATCH] chore: add coverage/lint/CVE CI gates, cover sigstore trust-root, bump deps Raise trust-root coverage and add the missing CI enforcement gates. Coverage - New tests/test_signing_unit.py (25 tests) drives signing.py's internal sign/verify paths directly, mocking the sigstore boundary (no network/OIDC). signing.py line coverage 61% -> 99%; whole-project 75% -> ~86%. - Enforce coverage in CI: pytest --cov=mcp_warden --cov-fail-under=80. - Capture subprocess coverage of guard_list_gate.py (0% -> ~89%) via [tool.coverage.run] parallel=true + COVERAGE_PROCESS_START, so the guard child spawned by the strict-abort tests is measured (no longer a 0% artifact). Lint - Add [tool.ruff] (E,W,F,I,B; line-length=100; ignore E501,B008) and a ruff lint job in the CI workflow. Fixed all surfaced violations (unused imports, import order, B904 raise-from, E702, F841). guard.py's GUARD_STRICT_EXIT and res_net.py's SSRF_NETWORKS re-exports are preserved via # noqa: F401 (both are consumed by tests / res_rules re-export; NOT dead). CVE - Add a fail-closed pip-audit gate to deps-locked that audits the resolved dependency closure. Deps - Bump the hash-locked dev/CI closure: pydantic-settings 2.14.1 -> 2.14.2 (clears GHSA-4xgf-cpjx-pc3j), mcp 1.27.2 -> 1.28.1, cryptography -> 49.0.0, anyio 4.13.0 -> 4.14.2, others. Add pytest-cov to the dev extra. --- .github/workflows/deps-locked.yml | 15 + .github/workflows/integrity-gate.yml | 51 +- .gitignore | 7 + CHANGELOG.md | 23 + pyproject.toml | 52 ++ requirements-dev.lock | 707 ++++++++++++++++--------- src/mcp_warden/cli_sign.py | 2 +- src/mcp_warden/guard.py | 6 +- src/mcp_warden/res_net.py | 5 +- tests/fixtures/fault_guard_launcher.py | 2 +- tests/fuzz/test_fuzz_domain.py | 1 - tests/test_action_yml.py | 2 - tests/test_capture_http.py | 2 - tests/test_guard_platform.py | 1 - tests/test_guard_proxy.py | 2 - tests/test_guard_strict.py | 4 +- tests/test_guard_v3.py | 2 - tests/test_guard_windows.py | 4 +- tests/test_precommit.py | 1 - tests/test_res_dns.py | 2 - tests/test_signing_unit.py | 382 +++++++++++++ 21 files changed, 998 insertions(+), 275 deletions(-) create mode 100644 tests/test_signing_unit.py diff --git a/.github/workflows/deps-locked.yml b/.github/workflows/deps-locked.yml index ed81a81..dd37d99 100644 --- a/.github/workflows/deps-locked.yml +++ b/.github/workflows/deps-locked.yml @@ -103,3 +103,18 @@ jobs: run: | /tmp/locked/bin/python -m pip install --no-deps -e . /tmp/locked/bin/python -c "import mcp_warden; from mcp_warden import cli; import mcp, rfc8785, pydantic, typer, rich, yaml, anyio, pytest, hypothesis; print('OK: mcp_warden + hash-locked dev/CI closure import clean')" + + # -------------------------------------------------------------------- + # 4. CVE gate (pip-audit, fail-closed). + # Audits the RESOLVED dependency closure against the PyPI/OSV advisory + # databases and fails the job on ANY known advisory. A supply-chain + # integrity tool must not ship its own vulnerable transitive deps — this + # is the standing evidence that we don't. --skip-editable excludes the + # editable mcp-warden-cli checkout itself (no published advisories for a + # local source tree). Mirrors conclave's fail-closed pip-audit job. + # -------------------------------------------------------------------- + - name: Install pip-audit (pinned) + run: /tmp/locked/bin/python -m pip install "pip-audit==2.9.0" + + - name: CVE audit the locked closure (fail on any advisory) + run: /tmp/locked/bin/python -m pip_audit --skip-editable diff --git a/.github/workflows/integrity-gate.yml b/.github/workflows/integrity-gate.yml index 8816289..bd3181f 100644 --- a/.github/workflows/integrity-gate.yml +++ b/.github/workflows/integrity-gate.yml @@ -37,11 +37,24 @@ jobs: python-version: "3.11" cache: pip - - name: Install package + dev deps - run: pip install -e ".[dev]" + # Install the sigstore extra too so the trust-root suite (test_signing*.py) + # exercises signing.py fully and the coverage number is reproducible run to + # run — without it the version-probe branches skip and coverage drifts. + - name: Install package + dev deps (with sigstore extra) + run: pip install -e ".[dev,sigstore]" - - name: Run pytest - run: pytest -q + # Coverage floor gate. --cov-fail-under=80 pins the whole-project floor + # below the current ~86% actual so it can only rise, never silently + # regress. COVERAGE_PROCESS_START makes the coverage .pth hook fire inside + # the guard child that tests/fixtures/fault_guard_launcher.py spawns as a + # real subprocess (see [tool.coverage.run] parallel=true in pyproject.toml), + # so guard_list_gate.py — which only ever executes in-subprocess — is + # measured instead of reporting a 0% visibility artifact. pytest-cov + # combines the parallel data files automatically. + - name: Run pytest with coverage (floor 80%) + env: + COVERAGE_PROCESS_START: pyproject.toml + run: pytest -q --cov=mcp_warden --cov-report=term-missing --cov-fail-under=80 # Re-run the property-fuzz suite deterministically with a FIXED hypothesis # seed so any counterexample is replayable from the workflow log alone @@ -53,6 +66,36 @@ jobs: - name: Run property-fuzz suite (deterministic, seed=0) run: pytest tests/fuzz -p no:randomly --hypothesis-seed=0 + # -------------------------------------------------------------------------- + # Job 1b: lint gate (ruff) + # + # ruff is the fast lint gate: pyflakes (F, dead imports/vars), pycodestyle + # (E/W), isort (I, import order) and bugbear (B, common bug patterns). Config + # lives in [tool.ruff] in pyproject.toml (line-length=100; E501 + the typer + # B008 default-call idiom are ignored). A single real violation fails CI. + # -------------------------------------------------------------------------- + lint: + name: Lint (ruff) + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Set up Python 3.11 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: "3.11" + cache: pip + + # Pinned ruff — this is the supply-chain tool's own CI, so the linter is + # version-pinned too (bump deliberately, never float). + - name: Install ruff (pinned) + run: pip install "ruff==0.15.21" + + - name: Ruff lint check + run: ruff check . + # -------------------------------------------------------------------------- # Job 2: MCP integrity gate demo # diff --git a/.gitignore b/.gitignore index e2f945f..a2bf761 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,14 @@ site/ .pytest_cache/ .ruff_cache/ .mypy_cache/ +.hypothesis/ uv.lock +# Coverage data (parallel data files + combined) +.coverage +.coverage.* +coverage.xml +htmlcov/ + # OS .DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 117bdc4..8c1d477 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,29 @@ CI. The v0.3 `guard` proxy adds deterministic runtime *result* inspection ### Added +- **CI coverage / lint / CVE gates.** The `CI` workflow now enforces three new + standing gates: (1) a **coverage floor** — `pytest --cov=mcp_warden + --cov-fail-under=80` (whole-project coverage is ~86%; the floor is pinned below + actual so it can only rise). Subprocess coverage of `guard_list_gate.py` (which + only executes inside the guard child spawned by the strict-abort tests) is now + captured via `[tool.coverage.run] parallel = true` + `COVERAGE_PROCESS_START`, + taking it from a 0% visibility artifact to ~89%. (2) A **ruff lint gate** + (`ruff check .`) with config in `[tool.ruff]` (pyflakes/pycodestyle/isort/ + bugbear; `line-length = 100`). (3) A **pip-audit CVE gate** in `deps-locked` + that audits the resolved dependency closure and fails closed on any advisory. +- **Trust-root unit tests (`tests/test_signing_unit.py`).** 25 new tests drive + the internal `signing.py` sign/verify code paths directly (mocking the sigstore + boundary — no network, OIDC, or Fulcio/Rekor traffic), lifting `signing.py` + line coverage from 61% to 99%. Covers the sign path (ambient + explicit token), + the verify path (identity/issuer plumbing), and every fail-closed branch. + +### Changed + +- **Dependency refresh (security).** Bumped the hash-locked dev/CI closure: + `pydantic-settings` 2.14.1 → 2.14.2 (clears advisory **GHSA-4xgf-cpjx-pc3j**), + plus current minors of `mcp` (1.27.2 → 1.28.1), `cryptography` (→ 49.0.0), + `anyio` (4.13.0 → 4.14.2), and others. `pytest-cov` added to the `dev` extra. + - **Runtime DNS resolution SSRF bypass detection (`WRD-RES-EXFIL-DNS-SSRF`)** (#11): the `guard` proxy now resolves URL hostnames from `tools/call` results at runtime and blocks (error-replace) when any resolved IP falls in a deny range diff --git a/pyproject.toml b/pyproject.toml index 1af9e95..6afd437 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ dependencies = [ dev = [ "pytest>=9.0.3", "pytest-asyncio>=0.23.0", + "pytest-cov>=6.0.0", "hypothesis>=6.100.0", ] # #16: Sigstore keyless signing + verification of warden.lock. OPT-IN — the base @@ -55,7 +56,58 @@ build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/mcp_warden"] +# -------------------------------------------------------------------------- +# Lint config (ruff). Modeled on conclave's config: pyflakes (F), pycodestyle +# (E/W), isort (I) and bugbear (B). line-length owns E501 so we don't fight the +# formatter over long lines; B008 is ignored because typer/FastAPI idiomatically +# put `typer.Option(...)` / `Depends(...)` calls in argument defaults, which B008 +# flags as false positives. +# -------------------------------------------------------------------------- +[tool.ruff] +line-length = 100 +target-version = "py311" +src = ["src", "tests"] + +[tool.ruff.lint] +select = ["E", "W", "F", "I", "B"] +ignore = ["E501", "B008"] + +[tool.ruff.lint.per-file-ignores] +# Tests intentionally reach into private module internals (leading underscore +# names) and re-bind them via monkeypatch; that is the whole point of the +# trust-root wiring tests, so don't flag those imports/uses. +"tests/**" = ["E402"] + [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] filterwarnings = ["ignore::DeprecationWarning"] + +# -------------------------------------------------------------------------- +# Coverage config (issue: coverage + lint/CVE gates). +# +# parallel = true so the guard child spawned via tests/fixtures/ +# fault_guard_launcher.py (a real subprocess — see test_guard_strict.py) is +# measured too. That subprocess inherits COVERAGE_PROCESS_START from the parent +# env and the coverage .pth hook starts a second data file; `coverage combine` +# (run by pytest-cov automatically) merges them. Without this, guard_list_gate.py +# reports 0% purely as a visibility artifact — it only ever runs in-subprocess. +# +# CI sets COVERAGE_PROCESS_START=pyproject.toml before pytest so the .pth hook +# fires in child processes; see .github/workflows/integrity-gate.yml. +# -------------------------------------------------------------------------- +[tool.coverage.run] +branch = false +parallel = true +source = ["mcp_warden"] + +[tool.coverage.report] +show_missing = true +# Exclude the guarded lines that are unreachable by design (the ImportError +# graceful-degrade path is exercised only via monkeypatch, never a real absent +# import) plus the standard pragma. +exclude_lines = [ + "pragma: no cover", + "if TYPE_CHECKING:", + "raise NotImplementedError", +] diff --git a/requirements-dev.lock b/requirements-dev.lock index 73717cb..569c8ce 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -8,9 +8,9 @@ annotated-types==0.7.0 \ --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 # via pydantic -anyio==4.13.0 \ - --hash=sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708 \ - --hash=sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc +anyio==4.14.2 \ + --hash=sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494 \ + --hash=sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f # via # mcp-warden-cli (pyproject.toml) # httpx @@ -23,101 +23,117 @@ attrs==26.1.0 \ # via # jsonschema # referencing -certifi==2026.5.20 \ - --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \ - --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d +certifi==2026.6.17 \ + --hash=sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432 \ + --hash=sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db # via # httpcore # httpx -cffi==2.0.0 ; platform_python_implementation != 'PyPy' \ - --hash=sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb \ - --hash=sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b \ - --hash=sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f \ - --hash=sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9 \ - --hash=sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44 \ - --hash=sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2 \ - --hash=sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c \ - --hash=sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75 \ - --hash=sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65 \ - --hash=sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e \ - --hash=sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a \ - --hash=sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e \ - --hash=sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25 \ - --hash=sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a \ - --hash=sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe \ - --hash=sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b \ - --hash=sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91 \ - --hash=sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592 \ - --hash=sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187 \ - --hash=sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c \ - --hash=sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1 \ - --hash=sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94 \ - --hash=sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba \ - --hash=sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb \ - --hash=sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165 \ - --hash=sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529 \ - --hash=sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca \ - --hash=sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c \ - --hash=sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6 \ - --hash=sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c \ - --hash=sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0 \ - --hash=sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743 \ - --hash=sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63 \ - --hash=sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5 \ - --hash=sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5 \ - --hash=sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4 \ - --hash=sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d \ - --hash=sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b \ - --hash=sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93 \ - --hash=sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205 \ - --hash=sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27 \ - --hash=sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512 \ - --hash=sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d \ - --hash=sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c \ - --hash=sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037 \ - --hash=sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26 \ - --hash=sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322 \ - --hash=sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb \ - --hash=sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c \ - --hash=sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8 \ - --hash=sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4 \ - --hash=sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414 \ - --hash=sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9 \ - --hash=sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664 \ - --hash=sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9 \ - --hash=sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775 \ - --hash=sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739 \ - --hash=sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc \ - --hash=sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062 \ - --hash=sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe \ - --hash=sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9 \ - --hash=sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92 \ - --hash=sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5 \ - --hash=sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13 \ - --hash=sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d \ - --hash=sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26 \ - --hash=sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f \ - --hash=sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495 \ - --hash=sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b \ - --hash=sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6 \ - --hash=sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c \ - --hash=sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef \ - --hash=sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5 \ - --hash=sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18 \ - --hash=sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad \ - --hash=sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3 \ - --hash=sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7 \ - --hash=sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5 \ - --hash=sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534 \ - --hash=sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49 \ - --hash=sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2 \ - --hash=sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5 \ - --hash=sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453 \ - --hash=sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf +cffi==2.1.0 ; platform_python_implementation != 'PyPy' \ + --hash=sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc \ + --hash=sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd \ + --hash=sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d \ + --hash=sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5 \ + --hash=sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f \ + --hash=sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6 \ + --hash=sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c \ + --hash=sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda \ + --hash=sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd \ + --hash=sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a \ + --hash=sha256:164bff1657b2a74f0b6d54e11c9b375bc97b931f2ca9c43fcf875838da1570dd \ + --hash=sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd \ + --hash=sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3 \ + --hash=sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb \ + --hash=sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66 \ + --hash=sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d \ + --hash=sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f \ + --hash=sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6 \ + --hash=sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0 \ + --hash=sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c \ + --hash=sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93 \ + --hash=sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d \ + --hash=sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d \ + --hash=sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8 \ + --hash=sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b \ + --hash=sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001 \ + --hash=sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d \ + --hash=sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43 \ + --hash=sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b \ + --hash=sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0 \ + --hash=sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0 \ + --hash=sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458 \ + --hash=sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8 \ + --hash=sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d \ + --hash=sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94 \ + --hash=sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022 \ + --hash=sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db \ + --hash=sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479 \ + --hash=sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376 \ + --hash=sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d \ + --hash=sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6 \ + --hash=sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3 \ + --hash=sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea \ + --hash=sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd \ + --hash=sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02 \ + --hash=sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde \ + --hash=sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224 \ + --hash=sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76 \ + --hash=sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804 \ + --hash=sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1 \ + --hash=sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913 \ + --hash=sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714 \ + --hash=sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc \ + --hash=sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2 \ + --hash=sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e \ + --hash=sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda \ + --hash=sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512 \ + --hash=sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28 \ + --hash=sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699 \ + --hash=sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3 \ + --hash=sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c \ + --hash=sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe \ + --hash=sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a \ + --hash=sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f \ + --hash=sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c \ + --hash=sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2 \ + --hash=sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f \ + --hash=sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b \ + --hash=sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565 \ + --hash=sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056 \ + --hash=sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629 \ + --hash=sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7 \ + --hash=sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0 \ + --hash=sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9 \ + --hash=sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853 \ + --hash=sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13 \ + --hash=sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a \ + --hash=sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4 \ + --hash=sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce \ + --hash=sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac \ + --hash=sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c \ + --hash=sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46 \ + --hash=sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384 \ + --hash=sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b \ + --hash=sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210 \ + --hash=sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc \ + --hash=sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a \ + --hash=sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5 \ + --hash=sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7 \ + --hash=sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2 \ + --hash=sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326 \ + --hash=sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f \ + --hash=sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca \ + --hash=sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98 \ + --hash=sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9 \ + --hash=sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5 \ + --hash=sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7 \ + --hash=sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc \ + --hash=sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da \ + --hash=sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f # via cryptography -click==8.4.1 ; sys_platform != 'emscripten' \ - --hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \ - --hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96 +click==8.4.2 ; sys_platform != 'emscripten' \ + --hash=sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6 \ + --hash=sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76 # via uvicorn colorama==0.4.6 ; sys_platform == 'win32' \ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ @@ -126,6 +142,99 @@ colorama==0.4.6 ; sys_platform == 'win32' \ # click # pytest # typer +coverage==7.15.1 \ + --hash=sha256:00b6703c6640075cdce5124e9335dfdf9167272475301828acfdd09c0e5ee731 \ + --hash=sha256:05d87c2a43373ad6b976d0a99ad58c48633633bcdeb896dc645a006472cc4a71 \ + --hash=sha256:09b3b088aa24489c4082bcc35fcc8224281ab94a653dfb6d3f0c8165b0d628ab \ + --hash=sha256:0a545ef5384d787d0fcac6c349afc2c5f99dcc39e13ed3c191b2c06305f64c04 \ + --hash=sha256:0c579056b0de461b3a62318b63d0b6ce90aed7f8158d3f00da094df82f29d189 \ + --hash=sha256:0f410ee8f0ac4ec7db71bc0b7632a8b9994e1cad2755bd1566c17e6a162caa74 \ + --hash=sha256:1174900a43f6f8c425fee10d7dbddc308adefcdc78aaced32357f5ab750a0e90 \ + --hash=sha256:165e9949eaf222ef1f018635d0d7f368a23bfe0212af558534c40d8c04686d67 \ + --hash=sha256:1678eb2dc57a8ce67601b029582ef6d41e9e6ca22692aaeccd4107e40f27386c \ + --hash=sha256:18ea20e3922d7f8ca9e0ef1084408d08c4ad62d5e531cb9c1f6896a99297ebea \ + --hash=sha256:1ce6947e2a95534ecaa5a15e73c21e550514c980d80eda204d064d789a95f6a4 \ + --hash=sha256:206d4ec6028f2773b40932d09f074539d6bcdd8f6b318d40cb04bdbd68ed0b49 \ + --hash=sha256:23214bdbe226f2b0e9c66a7d6a1d59d4a88045dcf86e702cf0fe0d0935e3d615 \ + --hash=sha256:24d8e85a2a45e44883b488c2659f51fa761dad5353fdb319b672a93facbd2ca9 \ + --hash=sha256:26a40cbf2b13bd94af53ee02a424cb3bb96a9edfac0d00834bd068512a62714b \ + --hash=sha256:26f89cf6d0634375f454fa71057945ad18edb0f1607a90fecf22c57dc3dc289a \ + --hash=sha256:2afce82f2cf8f4c9002746a42755e1dc61baff33d9f7ab5569b4c9101f8f4d1d \ + --hash=sha256:2b25f0f0fa5260df9d7bb55d47c8bdc23fa3382c1a18f7c9cae122e6c320b1ad \ + --hash=sha256:2de8ecbbc77c7e4d22572779920ed8979c69168675e96be3a548c996568c6c31 \ + --hash=sha256:345034976f46a1c54bd17f4e43eb30bb92cb7082fcddff03250cff136cc4eb82 \ + --hash=sha256:34fe7cf79d5f1f87f2e8ce7dc1c32950841f50e10d0120f263856acfad66de34 \ + --hash=sha256:359f3fbe09a51500c51966596ee4ee4070b356552c70b3b2420eb200d68e0f76 \ + --hash=sha256:3ad9a0eac4728327fd870d52f74d2e631d176c5f178eaea2d9983ab5b9755a55 \ + --hash=sha256:3c78f0cea7275342cf2adc2ad5fdd0aafa106ad91e66d573568f2fcf62c41df5 \ + --hash=sha256:3ea65e3ee6c7c32349fd00559927a9e577bdd72386087eeed1c42b62dfce9b82 \ + --hash=sha256:433a73200848e80f27712fc113b6ff5311f29b479a7d3bd4b1106138a77f9674 \ + --hash=sha256:4a2effcbd93ae340a58db718fe4181d967f84d352c4cefeaab4ff82ce813901a \ + --hash=sha256:4f051a64eb8f8addb4661c2b41d6eea5b7ebc68ad4b2baea8d9bc54e1956e5f7 \ + --hash=sha256:5bbe2a06e0a5e1404d9ffbdb49b819bbd6a3bb198ebea4c8dfe7ad9f1e1c2e81 \ + --hash=sha256:5d0297e6a070eadb49df7cddd0ab6f420b8b689dd8904c7dd815a323168fa57e \ + --hash=sha256:5d3de58b837375e7f4c0e1a088ccab5f655efb2fd7427b729df02c862a559633 \ + --hash=sha256:5d6fa45079db9fbeba0a69e3d91189f05301d6ac918162a53179d32fc9ed4910 \ + --hash=sha256:5e2d2536d2f57a354aa382ed303ac0e2e5c9522a508c05b998d26181b94163a7 \ + --hash=sha256:620482ef1c9f4e61f962e159325fe77dea59d16e39d9c9470d069053b244d864 \ + --hash=sha256:6506330b4a8dcf53b95bd84d8d0e817107cdb3fc1438e835029cdf0bc6612eb0 \ + --hash=sha256:664e279ed40599b8ed16f4db18d92a7e212c73129672bec8f5d96d4da48d2404 \ + --hash=sha256:6827ac0519be3fe91bf96b4060eb00d1d24f82649b29862cd75a3cfca248b02a \ + --hash=sha256:68931b5fe746ed4fdaa8892989cab9e6c35781eeb3b0ab2ded893d561e1b3652 \ + --hash=sha256:6f0bab60a582d415f0fb535ccff13ba334a47a1538f98913330a525d23bd535a \ + --hash=sha256:717d01e6e00bed56ad13306f19e0dd2f4f645ee8159d2c72c72301d6cfc7090c \ + --hash=sha256:71ac4ca1658ca99160fd58cc6967110e989c34b04627f24ed6ec9f70fb24571a \ + --hash=sha256:724e878b213b302ad46e9f2fc872d386613f20ebfc492a211482d917ea76c14f \ + --hash=sha256:764e045811f9c8cda436641f3f088283351d331a519b5807f19041cd0a68da1c \ + --hash=sha256:771caba880fee96493d18dfc465c318e08ab74e3bc2a3e4089e52514be6a6e54 \ + --hash=sha256:7911b02f57053adf8164ae63edb1c26574d24dfccabadc5268cf69310a69a358 \ + --hash=sha256:81572011fc1fc271317da35da593944daef7bfd507085e35751abbe702b74f69 \ + --hash=sha256:81e503d130a472ad1bd38199ecd35116b40d92bcd31e27a2cacde035381f2070 \ + --hash=sha256:841befdbc89b9c82435fc25b0f4f41858b6238693e45af758bec4cfc1968171c \ + --hash=sha256:8697b2edb57143546a24389efc11e1b000cd5800fc20d84f04edb601e4a7cfb8 \ + --hash=sha256:86bd37eabe39977216f630a7fc1b698e7f5e81a191c7186013245c6c3d313f9d \ + --hash=sha256:895e65c96aef0cecea250f6e35e9a32f11375514e1a0cb5210e0fda128c04e8e \ + --hash=sha256:8c7953c4128ef53b6ffb5f90d87c87d4ce26731df294760bb2314eb0e069e44b \ + --hash=sha256:8cc316264317b07a9e90d7f2b4188a15e36e9b54e651081b791b0515fa612a29 \ + --hash=sha256:8f765d13c08497687d0780cca66115c6aa4ba6703ad43b61e94fab9db689e3a3 \ + --hash=sha256:916fcf2214f56960e409561b37fc32a160a42b6e85483d0652d7b70fa55d707e \ + --hash=sha256:962aa56c1c9b016d681265880eb6acc9966029d2c4c559319cc43a1abbb9b59a \ + --hash=sha256:985657ebd707941de90d488d1cbb5efac20bdf81f7b91eba771624ccda4d36f4 \ + --hash=sha256:98847557a6859cadf693792ce89f440cb89692993f60dc6d3a7e35f3d340216f \ + --hash=sha256:a318112bb4f79d9d04766196d5a3388caa825908a6a9b052aa87de3d9aea7c61 \ + --hash=sha256:a524fca1a6f08927d9dc2d4c873cfb7bd7202c247f08b14bdc02424071b8b304 \ + --hash=sha256:a7625770f7720b49bb30d194ad2f8d50fab3c5177874af3d2399676f95f9c594 \ + --hash=sha256:a886af95f59edf67d5770fd3564d53f4a8af93f25f8c1d60d27e00d7f5674ee8 \ + --hash=sha256:aab9902a64b8390e3b56e539fddae1d79a267807fe5cb0c18d7d2f544ce867e2 \ + --hash=sha256:afaa144b8f5b3bc69fe0ce50d401c46b01ab264782553bfd05a3f98804524ecb \ + --hash=sha256:b1801963f9f44ae0c0f6d737bc7aeb2bbcde7d1fe7e3b43cddc1961af42d3b41 \ + --hash=sha256:b4ee9818e8bae3544379ad2c09b851c4fb886aaa8860d57a1c1316ddcb16db49 \ + --hash=sha256:bb5f6c2cf1ffd0bf2bd925c7cdcae9b4f208e9696d453ed51eb1f5fa0cc5b45b \ + --hash=sha256:bde0fe24083d0b7b3dbafa7a09f0796410af1afa2523f28f5f208d8340a4aaca \ + --hash=sha256:c337da8fca7ea93ab43f3868cfcde6cf6dad32c3906b273cfbad5d7390bc423b \ + --hash=sha256:c6829d9a3b55ad2b73ef5fda8302e5be03683789e88b1a079dcf4a773229c21d \ + --hash=sha256:c6db15c217693bdc3ca0b84de1ba9afafe1c14c26a8a29d77f4ed0de2b6132e2 \ + --hash=sha256:cc3656c9ecc27b36bd0907455b77f83c0069ca9ad4a66dec892b76c696eb6047 \ + --hash=sha256:ce2f05c14d077f406fefc4fa5e4f093ad0e0787549f6582535d6e28766f0361b \ + --hash=sha256:d385fc9b054e309ad3cecdc77b586d2af0c98aeec2fdb3773544586f366e817c \ + --hash=sha256:d4e47e7eea81a8ccf060a07627654151d929da62c7b715738387c200905cec89 \ + --hash=sha256:d70f3542cd38de85a9e257dcb1ac4c1ab4b6d7d2c2a645809207556628755d1c \ + --hash=sha256:d78aa537237212c4313aabe5e964b66acc86350ed19ebc56a3e202df33b6077b \ + --hash=sha256:d9476292594309db922cc841dd13b303b3c388f4c25d279884f7e2341c681f80 \ + --hash=sha256:db3403fdb7a94d5eb73e099befad8104d2a7d110a0f0d99df0de61c5d1fa756c \ + --hash=sha256:df164be93b46b4825cc39339440a05edc54c4d1d865ba4a60fd43d151a2a1cd3 \ + --hash=sha256:e1b6099490e5f88569c46b18605f556c3b30acc9a0a219cf7ef8fab8f7161ec4 \ + --hash=sha256:e55d24cada901963eed5bc89fa562aa033f0d84b9d3de4ecf363737c13aed11e \ + --hash=sha256:eb5fa75dc3d30e3a1b75da97973479b20ffa9b0641ff56d6e94b5f3e210daa54 \ + --hash=sha256:f1198bca9c0dd7c188aae1f185b0c0b5fc4f0a2b6909000858c29550320bdb07 \ + --hash=sha256:f4c5a5eff4ad4f9f7088fd3fc7a66d98d06566ee294b3b053309fb0a3b45be1e \ + --hash=sha256:f6d0a28b63a0d75f9ed5118105d1154fc3aa40a8605a30d5d87e3d043ad90fe7 \ + --hash=sha256:f837bae572c7869ffaa502e604c87e182543012831cf87aae4586ad090ac6dcf \ + --hash=sha256:f89f7453d6d46db14cf233e2cd8edcd78de2b9c49d4f1dc109590b4e5dbfbb74 \ + --hash=sha256:f8a51a8ec382c39d939cba0ab07ae949077ae4e842343bd4eed22d432358cea9 \ + --hash=sha256:fa75dc099c126e941a9c0baa8ebd2cbc78bd778687534fe410baf754f6d9e374 \ + --hash=sha256:fc868bab88e049d41fcd41766810d790a8b960053be2a45e060f5ce0d31d258b + # via pytest-cov cryptography==49.0.0 \ --hash=sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001 \ --hash=sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122 \ @@ -192,9 +301,65 @@ httpx-sse==0.4.3 \ --hash=sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc \ --hash=sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d # via mcp -hypothesis==6.155.2 \ - --hash=sha256:ae36880287c9c5defe9f199d3d2b67d9947a4da2a46e6c57373cbdf2345b20e1 \ - --hash=sha256:c85ce6dcd630a90ce501f1d1dd1bc84b97f5649ca8a27e134c8cbf5aa480b1a5 +hypothesis==6.156.6 \ + --hash=sha256:01bb8270c46b3ef53b0c2d23ff613ea506d609d06f936d823ea57c58b66b05f7 \ + --hash=sha256:02accb187617ebaebb120da931f799a3cb0df7c38706f97f9d022441d4faf533 \ + --hash=sha256:050c8c0815f88d47dd0875a92698d20d61639b7b721ee043a6d687c7f14ff7d8 \ + --hash=sha256:07f2bc9df1aeba80e12029c1618e2ee54abc440068c305d7075ffd6b85251843 \ + --hash=sha256:08796b674c0b31a5dd4119b2173823390055921588d13eb77324e861b00fd7f8 \ + --hash=sha256:1f81163d36d3763b09ffaef7c3a71e88174ca3e6816201fca9d1d159f448fdb5 \ + --hash=sha256:2d47054d0230f0dd9b6868fc030126c7a6c25527144272ff376cc4e9c39f7540 \ + --hash=sha256:32710718c22fe8c5571464e898bb87d282837b02617d6ad68130abf7cb4843cb \ + --hash=sha256:34a70a7b8226e34d658072d8fb81d03f97f0a75ceb536329a321b94ce2232fd6 \ + --hash=sha256:38cd0c4a7b9f809f1e23a4d15adfa9c5d99869b9afc327350a5e563350b78e48 \ + --hash=sha256:38ed3178526382d392d04ad699ad7a2e53845e521a09d40f1cbbc1e1ff63ba48 \ + --hash=sha256:42760873d6db1069d6edbaa355a61b9078a9950259efcfc72fc695741d7db7cd \ + --hash=sha256:455f09107ec07c78f2a83cb8fc19e23879c9d51cdc831de6f9cb6ec4059cb9af \ + --hash=sha256:4c108655960b58ded3ca71b2dc5c69fb2ba7e9c723aeb6106facec3892d09087 \ + --hash=sha256:4ca8cc26ea2d31d22cf7710e92951cfaa921f0f8aa1b6db33a5176335f583a4f \ + --hash=sha256:5291bd33c4704d274d7c214d5c200e77f372a06644f5cbbe96dcbe53cb2fbf10 \ + --hash=sha256:556b905767e36147918634a64356aa05d8c956576f00aee01eb351678f193908 \ + --hash=sha256:55f3ec50161b4a95bae63bff2b5166e45935b493013d3be30ede279bf6192318 \ + --hash=sha256:583a658162ee7e1a82155e3f146932a3a2269030294f3c83f5cf52fcaa0562c3 \ + --hash=sha256:59a8def90d9a5a9b67e1ac529e903a2363ceb6cf873c209da6b4284c5daab671 \ + --hash=sha256:5ad94e28aabf4db0d479297d43b8a2a01e7caaa9bdfccfdac7a4a3717e05b993 \ + --hash=sha256:5baec7943a14d106e982121dd4f74cfc5ef45e37c17f94fe49338d3d1377f38c \ + --hash=sha256:5bfa3c7b758f7278081c6bfec5f89b43c4eb075c0c9eb095323f7a9eb019b513 \ + --hash=sha256:5e0afdf79cceed20fcf0a9fb80d4064a9b2b53d4d4eecbac0e21208a13f5a31b \ + --hash=sha256:74137bef6d502305c3648b2ed1a9bb4bc05fb1025e96b30a2c092204c40fe097 \ + --hash=sha256:7489b9a8f9df8227edd6c7cd8b9ccfab2483bab24da6a474c175973ca2294f58 \ + --hash=sha256:7baca17f4803ad4aa151732326f3990baf54c3127df44aa872ac5bdf8a98a9a6 \ + --hash=sha256:7c8d37bebb6924729bc0bbf5852689df568842948abe4d93dd0ad3377adf76fa \ + --hash=sha256:8083806645f84243aade727f4978185caaa0b7190af4318673999ee15fdbf424 \ + --hash=sha256:84dc89caaf741a02f904ca7bd02b1af99650c75552868162290208aeecb70858 \ + --hash=sha256:8893b4da90e06828846c1b100c3414a7729d047a020d854c0899ae9339df0e70 \ + --hash=sha256:9367ae25dfa6dc1af37904785e43c4f8fe1c4118cafdc2f06514154fbdd90992 \ + --hash=sha256:96de02faefa3ce079873541da96f42595583bb001e8e4219294ed7d4501cc4cc \ + --hash=sha256:983a5cfd955994bffc7eb02976241f7a1f3c2d94dbc3389430c45858fa5c1ae0 \ + --hash=sha256:a2a728b514fceb81e3f0464508911d5220fd74dadc3270f859427a686b60c4cf \ + --hash=sha256:a922eedcd8618f9c2e17b79fa7b3f3f0b2df34e201958611cc3f0f46cca33c10 \ + --hash=sha256:afec0631a7a557acb50f665108b5d1d1123c21bc702d156a740db1cc33be4a7d \ + --hash=sha256:b4e66aaa7385538a5d617174d47c198ee807f06de99e282a67c6cb724c69340d \ + --hash=sha256:b5f519905ddeb10e23b8ba2c254541a5b1a8f146fe0551be94d972f4a77226f4 \ + --hash=sha256:b7fc0b7df9b28d028e4cc295b2ac8fbbbc22e090a23382c92fff5e37696be74f \ + --hash=sha256:c3363d3fb8015594636689572510bb6090602d8e8e838a5693c2d52d3b5b09d8 \ + --hash=sha256:c3f2604b28d16d696aaaf4954d20f907b27e54034df98e64746a20c74c319f03 \ + --hash=sha256:c4a787c3af0035846034461792f2a62f1c43af850feb970a5b53a629d64b52fb \ + --hash=sha256:c574c3224563d730848bc5d1ef1683c4f83993400c0167899fe328f4bfcd4725 \ + --hash=sha256:c76634c45a3ceee4c4fdfed39aebd08b8b822ec8b0c556877ef82846fd777730 \ + --hash=sha256:c7c3f067166bfc28b67f0042fc5fdcc87b3b58664da0c2f563fe544448b7ab3b \ + --hash=sha256:caf6a93d011c10972da111c38ceb34ced20feaa8581e2b350c0655b022e27875 \ + --hash=sha256:cbc2ec7b7d905e6b6ec1635f6340bfa52aaab718101c59f052bc012a6b486cd8 \ + --hash=sha256:d0db1f4573800c618773622f03cb6533bb3377430ef938c9476ba10c39d22591 \ + --hash=sha256:d4ea6559c13606e13b645927f2e0906e52b5ac5d99b40d3abaaeb2e8c7ceeb75 \ + --hash=sha256:e96570ca5cdd9a5f2ff9e80a6fb2fd5420ebf33b833d7de5b09b6ebb26a3eb6c \ + --hash=sha256:eb7e9f8343bc6b948937e6ec12e6879ed25a17b53ceccbd2b84adadd3d511698 \ + --hash=sha256:f0d73edab7b8a0051b3634f2d04d62b7e7282f8f274963b11188ee4957d672ef \ + --hash=sha256:f1969646beead7d8cf6a2537d2765af89d73056e2cb218e7fae92b83802250a3 \ + --hash=sha256:f57842c1c5314839bdf4be5cff108589ff435cd7192c035dc48e6f14032915a5 \ + --hash=sha256:f9600277defbfa769d8a6af264cbdff16294682c210c2d058ef39348fec16c0c \ + --hash=sha256:f9631cd604ae6032c3edf99160dc1b9e33873f2e52762246b24f07fb758652ae \ + --hash=sha256:ffe012ad66dbe7b8e8ddef6f6992ab1b36719ea64430c2bf1ff7135521052a15 # via mcp-warden-cli (pyproject.toml) idna==3.18 \ --hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \ @@ -218,9 +383,9 @@ markdown-it-py==4.2.0 \ --hash=sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49 \ --hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a # via rich -mcp==1.27.2 \ - --hash=sha256:8e02db104096d1c25b28e64bde29a5c32b31bc241710213e12fd4d84985bdfef \ - --hash=sha256:d6ff5160c6ca65d93013626efb3fc249de683c30b2d8570755ceddd490344de5 +mcp==1.28.1 \ + --hash=sha256:2726bca5e7193f61c5dde8b12500a6de2d9acf6d1a1c0be9e8c2e706437991df \ + --hash=sha256:d51e36a5f5644faea4f85ea649bfffa6bc6c26770d42798ad6a3de3d2ba69683 # via mcp-warden-cli (pyproject.toml) mdurl==0.1.2 \ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ @@ -233,7 +398,9 @@ packaging==26.2 \ pluggy==1.6.0 \ --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - # via pytest + # via + # pytest + # pytest-cov pycparser==3.0 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy' \ --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992 @@ -367,9 +534,9 @@ pydantic-core==2.46.4 \ --hash=sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff \ --hash=sha256:fd8b3d9fd264be37976686c7f65cd52a83f5e84f4bfd2adf9c1d469676bbb6ae # via pydantic -pydantic-settings==2.14.1 \ - --hash=sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de \ - --hash=sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa +pydantic-settings==2.14.2 \ + --hash=sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440 \ + --hash=sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f # via mcp pygments==2.20.0 \ --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \ @@ -381,16 +548,21 @@ pyjwt==2.13.0 \ --hash=sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423 \ --hash=sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728 # via mcp -pytest==9.0.3 \ - --hash=sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 \ - --hash=sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c +pytest==9.1.1 \ + --hash=sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313 \ + --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c # via # mcp-warden-cli (pyproject.toml) # pytest-asyncio + # pytest-cov pytest-asyncio==1.4.0 \ --hash=sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1 \ --hash=sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42 # via mcp-warden-cli (pyproject.toml) +pytest-cov==7.1.0 \ + --hash=sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2 \ + --hash=sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678 + # via mcp-warden-cli (pyproject.toml) python-dotenv==1.2.2 \ --hash=sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a \ --hash=sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3 @@ -513,137 +685,123 @@ rich==15.0.0 \ # via # mcp-warden-cli (pyproject.toml) # typer -rpds-py==2026.5.1 \ - --hash=sha256:01d17b29c0c23d82b1f4751147ec49cf451f1fc2554eb9ef5f957e55d2656ead \ - --hash=sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a \ - --hash=sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4 \ - --hash=sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256 \ - --hash=sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb \ - --hash=sha256:0a5ae4dbe43c1076983b72616496919872ae7bbe7a1e21cc48336bc3154d130b \ - --hash=sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870 \ - --hash=sha256:0b35217adefe87f2fe4db7e9766cabe84744bfe9616d9667be18988928c7f2dc \ - --hash=sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08 \ - --hash=sha256:141c9498daf2ace9eda35d2b0e376f9ea8b058d84f2aef4f96fccfd449a2f251 \ - --hash=sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473 \ - --hash=sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b \ - --hash=sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a \ - --hash=sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131 \ - --hash=sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9 \ - --hash=sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01 \ - --hash=sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba \ - --hash=sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad \ - --hash=sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db \ - --hash=sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d \ - --hash=sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0 \ - --hash=sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63 \ - --hash=sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee \ - --hash=sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7 \ - --hash=sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b \ - --hash=sha256:3397a5ed7174dc2786bb214030232fc36fe8e5584fec43a9952cc542b1a12036 \ - --hash=sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb \ - --hash=sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16 \ - --hash=sha256:3684a59b158a7683aaeb8e25352e9a9dd2122cec78f2d8530266e4f91b4c7b3f \ - --hash=sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d \ - --hash=sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d \ - --hash=sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5 \ - --hash=sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78 \ - --hash=sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66 \ - --hash=sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972 \ - --hash=sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd \ - --hash=sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89 \ - --hash=sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732 \ - --hash=sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02 \ - --hash=sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef \ - --hash=sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a \ - --hash=sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c \ - --hash=sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723 \ - --hash=sha256:613fc4ee9eaef26dc5840666214dd6fbcebcf32f46e76f4abc473059f4e13dda \ - --hash=sha256:6142dbd80c4df62a5d899f0d616d417f84e0bc8d32526c8e5589019d75d028a7 \ - --hash=sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca \ - --hash=sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02 \ - --hash=sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015 \ - --hash=sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1 \ - --hash=sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed \ - --hash=sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00 \ - --hash=sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a \ - --hash=sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195 \ - --hash=sha256:6f249f8b860a200ad35193af961183ebe9132710484e6f6ce0cf89fd83c63a9a \ - --hash=sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa \ - --hash=sha256:7559f72b94ae52659086c595dfa017cde03155f7832071d30959049052cb3ece \ - --hash=sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df \ - --hash=sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26 \ - --hash=sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa \ - --hash=sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842 \ - --hash=sha256:7bd530e6a530bb3ea892f194fafa455f3516ac25ecf7143fd33c09be62b0470a \ - --hash=sha256:8213afbe8a3a906fb9acb2014423fe3359ee783d0bf90995f70623a3217bfa6c \ - --hash=sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd \ - --hash=sha256:85264a90ff4c05c1568dd65f5921c837614b67c60358fb4c17df3b7f2e90690a \ - --hash=sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf \ - --hash=sha256:8895840ac4809e5f60c88fd07617cd71326e73d6e5a8aa783c5c0f7c24985de2 \ - --hash=sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f \ - --hash=sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf \ - --hash=sha256:8c43a8a973270fd173bf48cdf80bbe66312421cba68d40845034f174f2389049 \ - --hash=sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3 \ - --hash=sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964 \ - --hash=sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291 \ - --hash=sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14 \ - --hash=sha256:99ab6ba7bfa2cb0f96a04e3652355bf04e3f51aceb1e943b8541dab7ba4828cc \ - --hash=sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47 \ - --hash=sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5 \ - --hash=sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d \ - --hash=sha256:9e25b7088f9ccbfc0dfcaa52bf969300ca229e10ecf758974ebcbb080a4b37bb \ - --hash=sha256:a04df86b3f0fade39ec8fd0e0aab089b1da9fbd2b48df778a57ef96f5e7d38df \ - --hash=sha256:a05fa4f41f37ec97c9c260441a940450a192f78d774d2b097eee1379f1e1246a \ - --hash=sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc \ - --hash=sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc \ - --hash=sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46 \ - --hash=sha256:ad3773236e95f7f33991eb125224b7da66f206504d032a253a02da7e134519fb \ - --hash=sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2 \ - --hash=sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e \ - --hash=sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb \ - --hash=sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec \ - --hash=sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325 \ - --hash=sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600 \ - --hash=sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559 \ - --hash=sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41 \ - --hash=sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644 \ - --hash=sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b \ - --hash=sha256:b95d5e11fc712b752081183a55a244c03cd00570489edd7014d8899f8ceb8162 \ - --hash=sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83 \ - --hash=sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038 \ - --hash=sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6 \ - --hash=sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b \ - --hash=sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3 \ - --hash=sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9 \ - --hash=sha256:c74005a7bb87752acf351c93897ec63ad77a07a0da7ecad9c050e32e7286ba34 \ - --hash=sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6 \ - --hash=sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb \ - --hash=sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa \ - --hash=sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6 \ - --hash=sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d \ - --hash=sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24 \ - --hash=sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838 \ - --hash=sha256:d0efbe45632665e53e3db8fe1e5692db58fc5cb9bab4459d570b83efefe11164 \ - --hash=sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97 \ - --hash=sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4 \ - --hash=sha256:df1d2a1996755b24b9ecee92cb4d36c28f86f464a6a173349c26bab41e94b8c2 \ - --hash=sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55 \ - --hash=sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3 \ - --hash=sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2 \ - --hash=sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358 \ - --hash=sha256:e4abbf391a70be864920858bf360f4fb380577c9a0f732438a1996726e2c195b \ - --hash=sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8 \ - --hash=sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0 \ - --hash=sha256:edf2765d84e42447f112ad877af8fe1db0089aaec5b28e88d6eab45e7fe99cea \ - --hash=sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081 \ - --hash=sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d \ - --hash=sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1 \ - --hash=sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81 \ - --hash=sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3 \ - --hash=sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8 \ - --hash=sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1 \ - --hash=sha256:fe71bca7d547acb17027c7fd1624ff8aae623499c498d3e7011182c4de5c25e0 \ - --hash=sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd +rpds-py==2026.6.3 \ + --hash=sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5 \ + --hash=sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680 \ + --hash=sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9 \ + --hash=sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538 \ + --hash=sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804 \ + --hash=sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf \ + --hash=sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4 \ + --hash=sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97 \ + --hash=sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6 \ + --hash=sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96 \ + --hash=sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a \ + --hash=sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187 \ + --hash=sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975 \ + --hash=sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f \ + --hash=sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703 \ + --hash=sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9 \ + --hash=sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127 \ + --hash=sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f \ + --hash=sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa \ + --hash=sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05 \ + --hash=sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171 \ + --hash=sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba \ + --hash=sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c \ + --hash=sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223 \ + --hash=sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4 \ + --hash=sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885 \ + --hash=sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698 \ + --hash=sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f \ + --hash=sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7 \ + --hash=sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed \ + --hash=sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f \ + --hash=sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf \ + --hash=sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e \ + --hash=sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f \ + --hash=sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24 \ + --hash=sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a \ + --hash=sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41 \ + --hash=sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc \ + --hash=sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d \ + --hash=sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146 \ + --hash=sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e \ + --hash=sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e \ + --hash=sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4 \ + --hash=sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12 \ + --hash=sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7 \ + --hash=sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261 \ + --hash=sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6 \ + --hash=sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5 \ + --hash=sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93 \ + --hash=sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7 \ + --hash=sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda \ + --hash=sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8 \ + --hash=sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342 \ + --hash=sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c \ + --hash=sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb \ + --hash=sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0 \ + --hash=sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77 \ + --hash=sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3 \ + --hash=sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885 \ + --hash=sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826 \ + --hash=sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617 \ + --hash=sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb \ + --hash=sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577 \ + --hash=sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80 \ + --hash=sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e \ + --hash=sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945 \ + --hash=sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90 \ + --hash=sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7 \ + --hash=sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0 \ + --hash=sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140 \ + --hash=sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822 \ + --hash=sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba \ + --hash=sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9 \ + --hash=sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4 \ + --hash=sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a \ + --hash=sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8 \ + --hash=sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf \ + --hash=sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4 \ + --hash=sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324 \ + --hash=sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53 \ + --hash=sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b \ + --hash=sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41 \ + --hash=sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9 \ + --hash=sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca \ + --hash=sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1 \ + --hash=sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d \ + --hash=sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690 \ + --hash=sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107 \ + --hash=sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2 \ + --hash=sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76 \ + --hash=sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d \ + --hash=sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af \ + --hash=sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6 \ + --hash=sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db \ + --hash=sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369 \ + --hash=sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd \ + --hash=sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911 \ + --hash=sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504 \ + --hash=sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a \ + --hash=sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9 \ + --hash=sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13 \ + --hash=sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc \ + --hash=sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278 \ + --hash=sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868 \ + --hash=sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2 \ + --hash=sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd \ + --hash=sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4 \ + --hash=sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6 \ + --hash=sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9 \ + --hash=sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00 \ + --hash=sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f \ + --hash=sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e \ + --hash=sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442 \ + --hash=sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da \ + --hash=sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90 \ + --hash=sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef # via # jsonschema # referencing @@ -655,9 +813,9 @@ sortedcontainers==2.4.0 \ --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 # via hypothesis -sse-starlette==3.4.4 \ - --hash=sha256:07e0fa0460138baf25cdd5fb28683472c3995dc1642225191b3832d62526bcb0 \ - --hash=sha256:3f4dd50d8aed2771a091f3a83000323fc3844541c16b4fe585ae2420cc6df973 +sse-starlette==3.4.5 \ + --hash=sha256:83072538bc211a2f68b7b0422226c4af3e9b62e106e07034664b832ca019842a \ + --hash=sha256:e71bad53323f65573c3864a6c3bd0c1eb6e5f092b2e48082b0c35927d19ca296 # via mcp starlette==1.3.1 \ --hash=sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0 \ @@ -665,13 +823,62 @@ starlette==1.3.1 \ # via # mcp # sse-starlette -typer==0.26.7 \ - --hash=sha256:5c87cfbc5d34491c5346ebf49c23e18d56ccb863268d3a8d592b26087c2f5e58 \ - --hash=sha256:e314a34c617e419c091b2830dda3ea1f257134ff593061a8f5b9717ab8dddb3a +tomli==2.4.1 ; python_full_version <= '3.11' \ + --hash=sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853 \ + --hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe \ + --hash=sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5 \ + --hash=sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d \ + --hash=sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd \ + --hash=sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26 \ + --hash=sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54 \ + --hash=sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6 \ + --hash=sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c \ + --hash=sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a \ + --hash=sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd \ + --hash=sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f \ + --hash=sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5 \ + --hash=sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9 \ + --hash=sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662 \ + --hash=sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9 \ + --hash=sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1 \ + --hash=sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585 \ + --hash=sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e \ + --hash=sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c \ + --hash=sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41 \ + --hash=sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f \ + --hash=sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085 \ + --hash=sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15 \ + --hash=sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7 \ + --hash=sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c \ + --hash=sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36 \ + --hash=sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076 \ + --hash=sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac \ + --hash=sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8 \ + --hash=sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232 \ + --hash=sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece \ + --hash=sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a \ + --hash=sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897 \ + --hash=sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d \ + --hash=sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4 \ + --hash=sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917 \ + --hash=sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396 \ + --hash=sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a \ + --hash=sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc \ + --hash=sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba \ + --hash=sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f \ + --hash=sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257 \ + --hash=sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30 \ + --hash=sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf \ + --hash=sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9 \ + --hash=sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049 + # via coverage +typer==0.26.8 \ + --hash=sha256:3512ca79ac5c11113414b36e80281b872884477722440691c89d1112e321a49c \ + --hash=sha256:c244a6bd558886fe3f8780efb6bdd28bb9aff005a94eedebaa5cb32926fe2f7e # via mcp-warden-cli (pyproject.toml) -typing-extensions==4.15.0 \ - --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ - --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 +typing-extensions==4.16.0 \ + --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \ + --hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5 # via # anyio # mcp @@ -688,7 +895,7 @@ typing-inspection==0.4.2 \ # mcp # pydantic # pydantic-settings -uvicorn==0.49.0 ; sys_platform != 'emscripten' \ - --hash=sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f \ - --hash=sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3 +uvicorn==0.51.0 ; sys_platform != 'emscripten' \ + --hash=sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b \ + --hash=sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0 # via mcp diff --git a/src/mcp_warden/cli_sign.py b/src/mcp_warden/cli_sign.py index 59d78ee..83b9b56 100644 --- a/src/mcp_warden/cli_sign.py +++ b/src/mcp_warden/cli_sign.py @@ -31,9 +31,9 @@ from .provenance import make_sigstore_pointer_attestation from .signing import ( VerificationError, + build_statement, bundle_from_json, bundle_to_json, - build_statement, sign_statement, verify_statement, ) diff --git a/src/mcp_warden/guard.py b/src/mcp_warden/guard.py index 9caaff4..863e72c 100644 --- a/src/mcp_warden/guard.py +++ b/src/mcp_warden/guard.py @@ -37,7 +37,11 @@ win32_release_child, ) from .guard_loop import GuardConfig, GuardState, handle_c2s, handle_s2c -from .guard_strict import GUARD_STRICT_EXIT, _find_strict_abort, _handle_strict_abort +from .guard_strict import ( + GUARD_STRICT_EXIT, # noqa: F401 — re-exported; tests import guard.GUARD_STRICT_EXIT + _find_strict_abort, + _handle_strict_abort, +) logger = logging.getLogger("mcp_warden.guard") diff --git a/src/mcp_warden/res_net.py b/src/mcp_warden/res_net.py index 2036fbf..2b2d873 100644 --- a/src/mcp_warden/res_net.py +++ b/src/mcp_warden/res_net.py @@ -11,7 +11,10 @@ import re from typing import Any -from .net_rules import SSRF_NETWORKS, parse_ip +from .net_rules import ( # noqa: F401 — SSRF_NETWORKS re-exported via res_rules + SSRF_NETWORKS, + parse_ip, +) #: Seed exfil denylist (RESULT_INSPECTION.md §3.3). Org-extensible at runtime. SEED_EXFIL_DENYLIST: tuple[str, ...] = ( diff --git a/tests/fixtures/fault_guard_launcher.py b/tests/fixtures/fault_guard_launcher.py index 7fb9dcf..9121a3a 100644 --- a/tests/fixtures/fault_guard_launcher.py +++ b/tests/fixtures/fault_guard_launcher.py @@ -54,8 +54,8 @@ def _install_fault(site: str) -> None: gl.evaluate_call = _make_raiser("evaluate_call") # type: ignore[assignment] elif site == "diverges": - import mcp_warden.guard_result as gr import mcp_warden.guard_list_gate as glg + import mcp_warden.guard_result as gr raiser = _make_raiser("diverges_from_lock") glg.diverges_from_lock = raiser # type: ignore[assignment] diff --git a/tests/fuzz/test_fuzz_domain.py b/tests/fuzz/test_fuzz_domain.py index dbe7140..03f943a 100644 --- a/tests/fuzz/test_fuzz_domain.py +++ b/tests/fuzz/test_fuzz_domain.py @@ -13,7 +13,6 @@ from hypothesis import assume, example, given from hypothesis import strategies as st -from mcp_warden import res_net from mcp_warden.res_net import ( SEED_EXFIL_DENYLIST, SEED_EXFIL_PATH_QUALIFIED, diff --git a/tests/test_action_yml.py b/tests/test_action_yml.py index b007a5a..db07aae 100644 --- a/tests/test_action_yml.py +++ b/tests/test_action_yml.py @@ -18,7 +18,6 @@ import re from pathlib import Path -import pytest import yaml ACTION_YML = Path(__file__).parent.parent / "action.yml" @@ -104,7 +103,6 @@ def test_exit_code_propagation_step_exists() -> None: propagation_found = False for step in steps: run_block = step.get("run", "") - name = step.get("name", "") step_id = step.get("id", "") if ( "exit" in run_block diff --git a/tests/test_capture_http.py b/tests/test_capture_http.py index 59a3bd2..cbc14c6 100644 --- a/tests/test_capture_http.py +++ b/tests/test_capture_http.py @@ -8,7 +8,6 @@ from __future__ import annotations import contextlib -from typing import Any from unittest.mock import AsyncMock, MagicMock, patch import pytest @@ -20,7 +19,6 @@ ) from mcp_warden.models import CapturedSurface - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- diff --git a/tests/test_guard_platform.py b/tests/test_guard_platform.py index d1b099c..db86814 100644 --- a/tests/test_guard_platform.py +++ b/tests/test_guard_platform.py @@ -34,7 +34,6 @@ platform_refusal_message, ) - # --- exit-code matrix invariant ------------------------------------------------ diff --git a/tests/test_guard_proxy.py b/tests/test_guard_proxy.py index bc97c56..fcfb0ae 100644 --- a/tests/test_guard_proxy.py +++ b/tests/test_guard_proxy.py @@ -18,8 +18,6 @@ import sys from pathlib import Path -import pytest - REPO = Path(__file__).resolve().parent.parent POISON = str(REPO / "tests" / "fixtures" / "poison_server.py") PY = sys.executable diff --git a/tests/test_guard_strict.py b/tests/test_guard_strict.py index f49531e..22281bc 100644 --- a/tests/test_guard_strict.py +++ b/tests/test_guard_strict.py @@ -88,7 +88,7 @@ def finish(self, timeout: float = 15.0) -> tuple[int, str]: except subprocess.TimeoutExpired: self.proc.kill() _out, err = self.proc.communicate() - raise AssertionError("guard hung; killed") + raise AssertionError("guard hung; killed") from None return self.proc.returncode, err.decode(errors="replace") @@ -562,7 +562,7 @@ def finish(self, timeout: float = 15.0) -> tuple[int, str]: except subprocess.TimeoutExpired: self.proc.kill() self.proc.communicate() - raise AssertionError("guard hung; killed") + raise AssertionError("guard hung; killed") from None return self.proc.returncode, err.decode(errors="replace") diff --git a/tests/test_guard_v3.py b/tests/test_guard_v3.py index 013e3eb..c4f02e0 100644 --- a/tests/test_guard_v3.py +++ b/tests/test_guard_v3.py @@ -20,8 +20,6 @@ import time from pathlib import Path -import pytest - REPO = Path(__file__).resolve().parent.parent FIX = REPO / "tests" / "fixtures" POISON = str(FIX / "poison_server.py") diff --git a/tests/test_guard_windows.py b/tests/test_guard_windows.py index c13027c..f8348f6 100644 --- a/tests/test_guard_windows.py +++ b/tests/test_guard_windows.py @@ -33,7 +33,6 @@ win32_release_child, ) - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- @@ -191,7 +190,8 @@ def test_register_child_noop_on_non_windows(monkeypatch): def test_release_child_pops_entry_and_closes_handle(monkeypatch): monkeypatch.setattr(lifecycle, "_IS_WINDOWS", True) _WIN_JOBS[77] = 9999 - k = MagicMock(); k.CloseHandle.return_value = 1 + k = MagicMock() + k.CloseHandle.return_value = 1 with _windll_patch(k): win32_release_child(77) assert 77 not in _WIN_JOBS diff --git a/tests/test_precommit.py b/tests/test_precommit.py index 6cd1d32..d57eb5c 100644 --- a/tests/test_precommit.py +++ b/tests/test_precommit.py @@ -19,7 +19,6 @@ import inspect from pathlib import Path -import pytest import yaml from mcp_warden import precommit diff --git a/tests/test_res_dns.py b/tests/test_res_dns.py index 2381c36..b002f21 100644 --- a/tests/test_res_dns.py +++ b/tests/test_res_dns.py @@ -3,11 +3,9 @@ from __future__ import annotations import socket -from unittest.mock import patch from mcp_warden.res_dns import extract_dns_candidates, resolve_ssrf_hits - # --------------------------------------------------------------------------- # extract_dns_candidates # --------------------------------------------------------------------------- diff --git a/tests/test_signing_unit.py b/tests/test_signing_unit.py new file mode 100644 index 0000000..5111063 --- /dev/null +++ b/tests/test_signing_unit.py @@ -0,0 +1,382 @@ +"""Unit tests for the sigstore trust-root wiring in :mod:`mcp_warden.signing`. + +The sibling ``test_signing.py`` exercises the CLI-level wiring by mocking the +``sign_statement`` / ``verify_statement`` seams from *cli_sign*. This module goes +one layer deeper: it drives the *internal* sign/verify code paths inside +``signing.py`` itself by monkeypatching the pinned sigstore boundary symbols +(``_ClientTrustConfig``, ``_SigningContext``, ``_Verifier``, ``_policy``, +``_IdentityToken``, ``_detect_credential``, ``_Bundle``). + +NO real network, OIDC, TUF, or Fulcio/Rekor traffic occurs — every sigstore +object is a local fake. This covers the sign path, the verify path, +certificate-identity/issuer plumbing, and the fail-closed error branches that the +CLI-level tests cannot reach (they stub the functions out entirely). +""" + +from __future__ import annotations + +import warnings + +import pytest + +from mcp_warden import signing +from mcp_warden.signing import ( + SigningError, + build_statement, + bundle_from_json, + bundle_to_json, + sign_statement, + verify_statement, +) + +_DIGEST = "sha256:" + "a" * 64 + + +# --- fakes for the sigstore boundary ----------------------------------------- + + +class _FakeSigner: + """Context-manager signer whose ``sign_artifact`` returns a sentinel bundle.""" + + def __init__(self, recorder: dict): + self._recorder = recorder + + def __enter__(self): + return self + + def __exit__(self, *exc): + return False + + def sign_artifact(self, statement_bytes: bytes): + self._recorder["signed_bytes"] = statement_bytes + return {"bundle": "signed", "over": statement_bytes} + + +def _install_fake_sign_boundary(monkeypatch, recorder: dict): + """Wire every sigstore sign-side symbol to a local fake (no network).""" + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", True) + + class _FakeTrustConfig: + @staticmethod + def production(): + return "trust-config-sentinel" + + class _FakeCtx: + def signer(self, identity): + recorder["identity"] = identity + return _FakeSigner(recorder) + + def _from_trust_config(trust_config): + recorder["trust_config"] = trust_config + return _FakeCtx() + + def _identity_token(raw): + recorder["raw_token"] = raw + return f"identity({raw})" + + monkeypatch.setattr(signing, "_ClientTrustConfig", _FakeTrustConfig) + monkeypatch.setattr( + signing, "_SigningContext", type("SC", (), {"from_trust_config": staticmethod(_from_trust_config)}) + ) + monkeypatch.setattr(signing, "_IdentityToken", _identity_token) + + +# --- _sigstore_version_tuple ------------------------------------------------- + + +def test_version_tuple_none_when_unavailable(monkeypatch): + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", False) + assert signing._sigstore_version_tuple() is None + + +def test_version_tuple_parses_installed_version(): + if not signing._SIGSTORE_AVAILABLE: + pytest.skip("sigstore extra not installed") + ver = signing._sigstore_version_tuple() + assert ver is not None + assert len(ver) == 3 + assert all(isinstance(n, int) for n in ver) + + +def test_version_tuple_pads_short_version(monkeypatch): + if not signing._SIGSTORE_AVAILABLE: + pytest.skip("sigstore extra not installed") + import sigstore + + monkeypatch.setattr(sigstore, "__version__", "4", raising=False) + assert signing._sigstore_version_tuple() == (4, 0, 0) + + +def test_version_tuple_returns_none_on_unparseable(monkeypatch): + if not signing._SIGSTORE_AVAILABLE: + pytest.skip("sigstore extra not installed") + import sigstore + + monkeypatch.setattr(sigstore, "__version__", "not.a.version", raising=False) + assert signing._sigstore_version_tuple() is None + + +# --- _warn_if_unpinned_version ---------------------------------------------- + + +def test_warn_noop_when_version_none(monkeypatch): + monkeypatch.setattr(signing, "_sigstore_version_tuple", lambda: None) + with warnings.catch_warnings(): + warnings.simplefilter("error") # any warning would raise + signing._warn_if_unpinned_version() # must NOT warn + + +def test_warn_noop_when_version_in_window(monkeypatch): + monkeypatch.setattr(signing, "_sigstore_version_tuple", lambda: (4, 3, 0)) + with warnings.catch_warnings(): + warnings.simplefilter("error") + signing._warn_if_unpinned_version() # in-window -> silent + + +def test_warn_below_window(monkeypatch): + monkeypatch.setattr(signing, "_sigstore_version_tuple", lambda: (4, 2, 9)) + with pytest.warns(RuntimeWarning, match="Crypto-API drift"): + signing._warn_if_unpinned_version() + + +def test_warn_above_window(monkeypatch): + monkeypatch.setattr(signing, "_sigstore_version_tuple", lambda: (5, 0, 0)) + with pytest.warns(RuntimeWarning, match="Crypto-API drift"): + signing._warn_if_unpinned_version() + + +# --- sign_statement: unavailable + identity resolution ----------------------- + + +def test_sign_unavailable_raises(monkeypatch): + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", False) + with pytest.raises(SigningError, match="mcp-warden\\[sigstore\\]"): + sign_statement(build_statement(_DIGEST), None) + + +def test_sign_ambient_no_credential_raises(monkeypatch): + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", True) + monkeypatch.setattr(signing, "_warn_if_unpinned_version", lambda: None) + monkeypatch.setattr(signing, "_detect_credential", lambda: None) + with pytest.raises(SigningError, match="no ambient OIDC credential"): + sign_statement(build_statement(_DIGEST), None) + + +def test_sign_ambient_empty_string_credential_raises(monkeypatch): + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", True) + monkeypatch.setattr(signing, "_warn_if_unpinned_version", lambda: None) + monkeypatch.setattr(signing, "_detect_credential", lambda: "") + with pytest.raises(SigningError, match="no ambient OIDC credential"): + sign_statement(build_statement(_DIGEST), None) + + +def test_sign_explicit_empty_token_raises(monkeypatch): + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", True) + monkeypatch.setattr(signing, "_warn_if_unpinned_version", lambda: None) + with pytest.raises(SigningError, match="empty"): + sign_statement(build_statement(_DIGEST), " ") + + +def test_sign_invalid_token_raises(monkeypatch): + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", True) + monkeypatch.setattr(signing, "_warn_if_unpinned_version", lambda: None) + + def _bad_identity(raw): + raise ValueError("malformed JWT") + + monkeypatch.setattr(signing, "_IdentityToken", _bad_identity) + with pytest.raises(SigningError, match="identity token is invalid"): + sign_statement(build_statement(_DIGEST), "not-a-jwt") + + +# --- sign_statement: success paths ------------------------------------------- + + +def test_sign_explicit_token_success(monkeypatch): + recorder: dict = {} + monkeypatch.setattr(signing, "_warn_if_unpinned_version", lambda: None) + _install_fake_sign_boundary(monkeypatch, recorder) + + statement = build_statement(_DIGEST) + bundle = sign_statement(statement, "my-explicit-token") + + assert bundle == {"bundle": "signed", "over": statement} + # The explicit token was passed straight through to IdentityToken. + assert recorder["raw_token"] == "my-explicit-token" + assert recorder["signed_bytes"] == statement + assert recorder["trust_config"] == "trust-config-sentinel" + + +def test_sign_ambient_token_success(monkeypatch): + recorder: dict = {} + monkeypatch.setattr(signing, "_warn_if_unpinned_version", lambda: None) + monkeypatch.setattr(signing, "_detect_credential", lambda: "ambient-token") + _install_fake_sign_boundary(monkeypatch, recorder) + + statement = build_statement(_DIGEST) + bundle = sign_statement(statement, None) + + assert bundle == {"bundle": "signed", "over": statement} + assert recorder["raw_token"] == "ambient-token" + + +def test_sign_signing_operation_failure_wrapped(monkeypatch): + """A failure inside the sigstore signer is wrapped as SigningError (fail closed).""" + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", True) + monkeypatch.setattr(signing, "_warn_if_unpinned_version", lambda: None) + monkeypatch.setattr(signing, "_IdentityToken", lambda raw: "identity") + + class _BoomTrustConfig: + @staticmethod + def production(): + raise RuntimeError("fulcio unreachable") + + monkeypatch.setattr(signing, "_ClientTrustConfig", _BoomTrustConfig) + with pytest.raises(SigningError, match="sigstore signing failed"): + sign_statement(build_statement(_DIGEST), "token") + + +def test_sign_signer_sign_artifact_failure_wrapped(monkeypatch): + """An exception raised inside sign_artifact is caught and re-raised closed.""" + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", True) + monkeypatch.setattr(signing, "_warn_if_unpinned_version", lambda: None) + monkeypatch.setattr(signing, "_IdentityToken", lambda raw: "identity") + + class _FailSigner: + def __enter__(self): + return self + + def __exit__(self, *exc): + return False + + def sign_artifact(self, _b): + raise RuntimeError("rekor rejected the entry") + + class _Ctx: + def signer(self, identity): + return _FailSigner() + + monkeypatch.setattr( + signing, "_ClientTrustConfig", type("TC", (), {"production": staticmethod(lambda: "cfg")}) + ) + monkeypatch.setattr( + signing, "_SigningContext", type("SC", (), {"from_trust_config": staticmethod(lambda cfg: _Ctx())}) + ) + with pytest.raises(SigningError, match="sigstore signing failed"): + sign_statement(build_statement(_DIGEST), "token") + + +# --- verify_statement -------------------------------------------------------- + + +def _install_fake_verify_boundary(monkeypatch, recorder: dict, *, verify_impl=None): + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", True) + monkeypatch.setattr(signing, "_warn_if_unpinned_version", lambda: None) + + class _FakeVerifier: + @staticmethod + def production(): + return _FakeVerifier() + + def verify_artifact(self, statement_bytes, bundle, policy): + recorder["statement"] = statement_bytes + recorder["bundle"] = bundle + recorder["policy"] = policy + if verify_impl is not None: + verify_impl(statement_bytes, bundle, policy) + + def _identity(identity, issuer): + recorder["identity"] = identity + recorder["issuer"] = issuer + return ("policy", identity, issuer) + + monkeypatch.setattr(signing, "_Verifier", _FakeVerifier) + monkeypatch.setattr(signing, "_policy", type("P", (), {"Identity": staticmethod(_identity)})) + + +def test_verify_unavailable_raises(monkeypatch): + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", False) + with pytest.raises(RuntimeError, match="mcp-warden\\[sigstore\\]"): + verify_statement(build_statement(_DIGEST), object(), "id@x", "https://issuer") + + +def test_verify_success_returns_none_and_plumbs_identity_issuer(monkeypatch): + recorder: dict = {} + _install_fake_verify_boundary(monkeypatch, recorder) + statement = build_statement(_DIGEST) + result = verify_statement(statement, {"b": 1}, "id@x.invalid", "https://issuer.invalid") + assert result is None + # Identity + issuer were threaded into policy.Identity exactly. + assert recorder["identity"] == "id@x.invalid" + assert recorder["issuer"] == "https://issuer.invalid" + assert recorder["statement"] == statement + assert recorder["bundle"] == {"b": 1} + assert recorder["policy"] == ("policy", "id@x.invalid", "https://issuer.invalid") + + +def test_verify_propagates_verification_error(monkeypatch): + recorder: dict = {} + + def _boom(statement, bundle, policy): + raise signing.VerificationError("certificate identity does not match") + + _install_fake_verify_boundary(monkeypatch, recorder, verify_impl=_boom) + with pytest.raises(signing.VerificationError, match="does not match"): + verify_statement(build_statement(_DIGEST), {}, "wrong@x", "https://issuer") + + +def test_verify_propagates_generic_error(monkeypatch): + recorder: dict = {} + + def _boom(statement, bundle, policy): + raise RuntimeError("TUF metadata refresh failed") + + _install_fake_verify_boundary(monkeypatch, recorder, verify_impl=_boom) + with pytest.raises(RuntimeError, match="TUF metadata"): + verify_statement(build_statement(_DIGEST), {}, "id@x", "https://issuer") + + +# --- bundle_to_json / bundle_from_json --------------------------------------- + + +def test_bundle_to_json_delegates(): + class _B: + def to_json(self): + return '{"round":"trip"}' + + assert bundle_to_json(_B()) == '{"round":"trip"}' + + +def test_bundle_from_json_unavailable_raises(monkeypatch): + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", False) + with pytest.raises(RuntimeError, match="mcp-warden\\[sigstore\\]"): + bundle_from_json('{"x":1}') + + +def test_bundle_from_json_delegates(monkeypatch): + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", True) + recorder: dict = {} + + class _FakeBundle: + @staticmethod + def from_json(text): + recorder["text"] = text + return {"parsed": text} + + monkeypatch.setattr(signing, "_Bundle", _FakeBundle) + out = bundle_from_json('{"real":"bundle"}') + assert out == {"parsed": '{"real":"bundle"}'} + assert recorder["text"] == '{"real":"bundle"}' + + +def test_bundle_from_json_propagates_parse_error(monkeypatch): + monkeypatch.setattr(signing, "_SIGSTORE_AVAILABLE", True) + + class _FakeBundle: + @staticmethod + def from_json(text): + raise ValueError("malformed bundle JSON") + + monkeypatch.setattr(signing, "_Bundle", _FakeBundle) + with pytest.raises(ValueError, match="malformed bundle"): + bundle_from_json("{not valid json")