From 26d13bdd9a2462b90dc11432e8f5d39edbf8b0d3 Mon Sep 17 00:00:00 2001 From: Addison Kline <77369109+addisonkline@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:17:01 -0400 Subject: [PATCH 1/2] feat: publish MAIL as five lockstep PyPI packages under mail-swarms-* Rework v2 packaging so `pip install mail-swarms` resolves a coherent set and only real (non-prerelease) releases reach PyPI. Packaging: - Rename the four members to mail-swarms-{client,server,daemon,protocol} (import names mail_client/etc. and CLI commands are unchanged); the originals were registered by others on PyPI. - Make mail-swarms a thin meta-package: metadata-only wheel (bypass-selection) + minimal sdist; no member trees, no legacy/ tree. - Pin members exactly (==2.0.1) in the meta and across all extras; pin members' internal mail-swarms-protocol dep exactly too. Bump all five to 2.0.1 in lockstep and refresh uv.lock. Release workflow: - Trigger on `released` (not `published`) + guard `!prerelease`, so prereleases can never publish. - Build/publish the whole workspace via `uv build --all-packages`. - Fail the run unless the git tag matches every package version. - skip-existing: true so a re-run / out-of-band upload is idempotent. Tooling & docs: - Add scripts/bump-version.py to bump all five versions + internal pins in lockstep (--check verifies agreement). - Update README/llms.txt/testing-plan and per-package doc titles to the new distribution names; regenerate THIRD_PARTY_NOTICES.md. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/python-publish.yml | 70 ++-- README.md | 16 +- THIRD_PARTY_NOTICES.md | 141 ++++++-- docs/testing-plan.md | 5 +- llms.txt | 302 +++++++++++++++++- pyproject.toml | 56 ++-- scripts/bump-version.py | 88 +++++ src/mail/client/README.md | 2 +- src/mail/client/docs/README.md | 4 +- src/mail/client/docs/reference/admin-panel.md | 4 +- src/mail/client/docs/reference/cli.md | 4 +- src/mail/client/docs/tutorials/quickstart.md | 8 +- src/mail/client/pyproject.toml | 6 +- src/mail/daemon/pyproject.toml | 8 +- src/mail/protocol/pyproject.toml | 4 +- src/mail/server/README.md | 2 +- src/mail/server/docs/README.md | 6 +- src/mail/server/docs/tutorials/quickstart.md | 4 +- src/mail/server/pyproject.toml | 8 +- uv.lock | 218 ++++++------- 20 files changed, 713 insertions(+), 243 deletions(-) create mode 100755 scripts/bump-version.py diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 6c4abe44..2ed5d1df 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,16 +1,18 @@ -# This workflow will upload a Python Package to PyPI when a release is created -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. +# This workflow uploads the MAIL Python packages to PyPI when a (non-prerelease) +# GitHub Release is published. +# +# MAIL ships as five lockstep distributions built from one uv workspace: +# mail-swarms (thin meta) -> mail-swarms-client, mail-swarms-server, +# mail-swarms-daemon, mail-swarms-protocol +# All five share a single version and are published together. name: Upload Python Package on: release: - types: [published] + # `released` fires only when a non-prerelease is published (or a prerelease + # is promoted to a full release); prereleases never trigger this workflow. + types: [released] permissions: contents: read @@ -18,19 +20,43 @@ permissions: jobs: release-build: runs-on: ubuntu-latest + # Defense-in-depth: skip the run for any release still flagged as a prerelease. + if: ${{ !github.event.release.prerelease }} + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v5 with: - python-version: "3.x" + python-version: "3.12" - - name: Build release distributions + - name: Verify release tag matches package versions run: | - # NOTE: put your own distribution build steps here. - python -m pip install build - python -m build + # Normalize "v2.0.1" -> "2.0.1"; every package must match exactly so a + # mis-tagged release fails loudly instead of publishing a wrong version. + tag="${RELEASE_TAG#v}" + echo "Release tag (normalized): $tag" + fail=0 + for f in pyproject.toml \ + src/mail/client/pyproject.toml \ + src/mail/server/pyproject.toml \ + src/mail/daemon/pyproject.toml \ + src/mail/protocol/pyproject.toml; do + ver="$(sed -nE 's/^version = "([^"]+)"$/\1/p' "$f" | head -1)" + if [ "$ver" != "$tag" ]; then + echo "::error file=$f::version is '$ver' but release tag is '$tag'" + fail=1 + else + echo "ok: $f = $ver" + fi + done + exit "$fail" + + - name: Build all workspace packages + run: uv build --all-packages - name: Upload distributions uses: actions/upload-artifact@v4 @@ -46,16 +72,10 @@ jobs: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write - # Dedicated environments with protections for publishing are strongly recommended. - # For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules + # Dedicated environment with protections for publishing. environment: name: pypi - # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status: url: https://pypi.org/p/mail-swarms - # - # ALTERNATIVE: if your GitHub Release name is the PyPI project version string - # ALTERNATIVE: exactly, uncomment the following line instead: - # url: https://pypi.org/project/YOURPROJECT/${{ github.event.release.name }} steps: - name: Retrieve release distributions @@ -64,7 +84,15 @@ jobs: name: release-dists path: dist/ + # Publishes every dist in dist/ (all five packages). PyPI matches each file + # to its project and verifies this repo/workflow/environment is a trusted + # publisher for THAT project, so every project needs a trusted publisher + # configured pointing at this workflow + the `pypi` env. - name: Publish release distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist/ + # Idempotent: skip files already on PyPI instead of failing. Lets a + # re-run (or a version published out-of-band) succeed rather than erroring + # on PyPI's no-reupload rule. + skip-existing: true diff --git a/README.md b/README.md index cd454dcc..0c90a03e 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,10 @@ runtime is archived under `src/mail/legacy`. ## Active v2 Packages -- `src/mail/protocol` - shared protocol types and constants (`mail-protocol`) -- `src/mail/server` - FastAPI server implementation (`mail-server`) -- `src/mail/client` - command-line client (`mail-client`) -- `src/mail/daemon` - daemon implementation (`mail-daemon`) +- `src/mail/protocol` - shared protocol types and constants (`mail-swarms-protocol`) +- `src/mail/server` - FastAPI server implementation (`mail-swarms-server`) +- `src/mail/client` - command-line client (`mail-swarms-client`) +- `src/mail/daemon` - daemon implementation (`mail-swarms-daemon`) ## Repository Layout @@ -21,10 +21,10 @@ mail/ ├── docs/ # v2 repository-level docs ├── spec/ # protocol specification and schemas ├── src/mail/ -│ ├── protocol/ # mail-protocol package -│ ├── server/ # mail-server package -│ ├── client/ # mail-client package -│ ├── daemon/ # mail-daemon package +│ ├── protocol/ # mail-swarms-protocol package +│ ├── server/ # mail-swarms-server package +│ ├── client/ # mail-swarms-client package +│ ├── daemon/ # mail-swarms-daemon package │ └── legacy/ # archived MAIL v1 runtime, docs, config, and UI ├── tests/ # active MAIL v2 test suite ├── scripts/ # repository maintenance scripts diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 9f1f9726..a6b7750d 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -5247,42 +5247,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` -## `mail-client==2.0.0` -CLI client for interacting with MAIL servers -### License field: -(license metadata not found) -### Warning: -No license metadata discovered; manual review needed. - -## `mail-daemon==2.0.0` -Daemon implementation for the Multi-Agent Interface Layer (MAIL) -### License field: -(license metadata not found) -### Warning: -No license metadata discovered; manual review needed. - -## `mail-protocol==2.0.0` -Types and constants for the Multi-Agent Interface Layer (MAIL) protocol -### License field: -(license metadata not found) -### Warning: -No license metadata discovered; manual review needed. - -## `mail-server==2.0.0` -FastAPI server for the Multi-Agent Interface Layer (MAIL) -### License field: -(license metadata not found) -### Warning: -No license metadata discovered; manual review needed. - -## `mail-swarms==2.0.0` +## `mail-swarms==2.0.1` Multi-Agent Interface Layer v2 workspace meta-package ### License field: Apache-2.0 ### Classifiers: License :: OSI Approved :: Apache Software License -### License Text (`mail_swarms-2.0.0.dist-info/licenses/LICENSE`) +### License Text (`mail_swarms-2.0.1.dist-info/licenses/LICENSE`) ```text Apache License @@ -5488,7 +5460,7 @@ See the License for the specific language governing permissions and limitations under the License. ``` -### License Text (`mail_swarms-2.0.0.dist-info/licenses/NOTICE`) +### License Text (`mail_swarms-2.0.1.dist-info/licenses/NOTICE`) ```text MAIL Project @@ -5502,6 +5474,34 @@ Third-party notices and attributions may be included by their respective projects and licenses in their source files or package metadata. ``` +## `mail-swarms-client==2.0.1` +CLI client for interacting with MAIL servers +### License field: +(license metadata not found) +### Warning: +No license metadata discovered; manual review needed. + +## `mail-swarms-daemon==2.0.1` +Daemon implementation for the Multi-Agent Interface Layer (MAIL) +### License field: +(license metadata not found) +### Warning: +No license metadata discovered; manual review needed. + +## `mail-swarms-protocol==2.0.1` +Types and constants for the Multi-Agent Interface Layer (MAIL) protocol +### License field: +(license metadata not found) +### Warning: +No license metadata discovered; manual review needed. + +## `mail-swarms-server==2.0.1` +FastAPI server for the Multi-Agent Interface Layer (MAIL) +### License field: +(license metadata not found) +### Warning: +No license metadata discovered; manual review needed. + ## `markdown-it-py==4.0.0` Python port of markdown-it. Markdown parsing, done right! ### License field: @@ -9720,6 +9720,85 @@ Copyright 2014 Ian Cordasco, Cory Benfield limitations under the License. ``` +## `respx==0.23.1` +A utility for mocking out the Python HTTPX and HTTP Core libraries. +### License field: +BSD-3-Clause +### Classifiers: +License :: OSI Approved :: BSD License +### Home page: +https://lundberg.github.io/respx/ +### Author: +Jonas Lundberg + +### License Text (`LICENSE.md`) + +```text +BSD 3-Clause License + +Copyright (c) 2019, 5 Monkeys Agency AB +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +### License Text (`respx-0.23.1.dist-info/LICENSE.md`) + +```text +BSD 3-Clause License + +Copyright (c) 2019, 5 Monkeys Agency AB +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + ## `rfc3339-validator==0.1.4` A pure python RFC3339 validator ### License field: diff --git a/docs/testing-plan.md b/docs/testing-plan.md index 115e04ac..462a2ce1 100644 --- a/docs/testing-plan.md +++ b/docs/testing-plan.md @@ -2,8 +2,9 @@ **Status:** In effect — Phases 0–5 landed **Date:** 2026-06-12 -**Scope:** The v2 packages (`mail-protocol`, `mail-server`, `mail-daemon`, -`mail-client`) and the repository-level `tests/` suite. The legacy suite under +**Scope:** The v2 packages (`mail-swarms-protocol`, `mail-swarms-server`, +`mail-swarms-daemon`, `mail-swarms-client`) and the repository-level `tests/` +suite. The legacy suite under `src/mail/legacy/tests/` is out of scope and remains frozen. --- diff --git a/llms.txt b/llms.txt index 22b65b9a..99e0fb18 100644 --- a/llms.txt +++ b/llms.txt @@ -11,10 +11,10 @@ runtime is archived under `src/mail/legacy`. ## Active v2 Packages -- `src/mail/protocol` - shared protocol types and constants (`mail-protocol`) -- `src/mail/server` - FastAPI server implementation (`mail-server`) -- `src/mail/client` - command-line client (`mail-client`) -- `src/mail/daemon` - daemon implementation (`mail-daemon`) +- `src/mail/protocol` - shared protocol types and constants (`mail-swarms-protocol`) +- `src/mail/server` - FastAPI server implementation (`mail-swarms-server`) +- `src/mail/client` - command-line client (`mail-swarms-client`) +- `src/mail/daemon` - daemon implementation (`mail-swarms-daemon`) ## Repository Layout @@ -23,10 +23,10 @@ mail/ ├── docs/ # v2 repository-level docs ├── spec/ # protocol specification and schemas ├── src/mail/ -│ ├── protocol/ # mail-protocol package -│ ├── server/ # mail-server package -│ ├── client/ # mail-client package -│ ├── daemon/ # mail-daemon package +│ ├── protocol/ # mail-swarms-protocol package +│ ├── server/ # mail-swarms-server package +│ ├── client/ # mail-swarms-client package +│ ├── daemon/ # mail-swarms-daemon package │ └── legacy/ # archived MAIL v1 runtime, docs, config, and UI ├── tests/ # active MAIL v2 test suite ├── scripts/ # repository maintenance scripts @@ -65,7 +65,7 @@ Legacy tests and other v1 material live under `src/mail/legacy`. Run archived legacy tests explicitly: ```bash -uv run pytest src/mail/legacy/tests +uv run --extra legacy pytest src/mail/legacy/tests ``` ## Documentation @@ -95,6 +95,285 @@ specification materials are covered by their repository license files. ===== End of `README.md` ===== +===== `docs/testing-plan.md` ===== + +# MAIL v2 Testing Suite Overhaul Plan + +**Status:** In effect — Phases 0–5 landed +**Date:** 2026-06-12 +**Scope:** The v2 packages (`mail-swarms-protocol`, `mail-swarms-server`, +`mail-swarms-daemon`, `mail-swarms-client`) and the repository-level `tests/` +suite. The legacy suite under +`src/mail/legacy/tests/` is out of scope and remains frozen. + +--- + +## 1. Background + +The v2 codebase (~99 source files, ~11.9k LOC across four packages) has +outpaced its test suite by roughly 8:1 in commit volume. The existing suite +(91 tests, all under `tests/unit/`) is well-constructed but covers only the +mailing-lists feature plus CLI parser shape — an estimated 5–8% of the v2 +surface. Major subsystems with **zero** coverage today: + +- the auth layer (`mail_server.auth`: JWT issue/verify, argon2 hashing, + role-validation dependencies) — existing endpoint tests monkeypatch it away +- every server router except `lists` (inbox, outbox, drafts, trash, swarms, + admin, daemon, auth — ~40 endpoints) +- the webhook delivery pipeline (HMAC-SHA256 signing, `X-MAIL-Signature`, + 6-step retry ladder) — six recent fix commits shipped with no tests +- all `mail_client` command behavior (only parser shape is tested) +- the entire `mail_daemon` package +- ~25 of 30 `mail_protocol` validators and most model `summarize()` paths +- ~40 `MemoryBackend` methods beyond the list-store group + +One test currently fails +(`test_mail_lists_endpoints.py::test_subscribe_other_rejected_with_403`) due +to drift from commit `570a340` — see Open Decisions (§7). + +## 2. Goals + +1. Establish four test categories — **unit**, **integration**, **contract**, + **e2e** — with clear ownership boundaries, so every future v2 change has an + obvious place for its tests. +2. Cover the subsystems where bugs have actually shipped (webhooks, auth, + routers) first. +3. Make spec drift mechanically detectable: the implementation, `spec/SPEC.md`, + and `spec/openapi.yaml` must not be able to diverge silently. +4. Wire coverage measurement so the gap stays visible. + +### Non-goals + +- Restoring or extending the legacy (`mail.legacy.*`) test suite. +- Performance/load testing (revisit after v2 stabilizes). +- Testing `daemon_deliver_remote` and other interswarm paths beyond stub + tracking — the feature itself is not implemented yet. + +## 3. Target suite architecture + +### 3.1 Directory layout + +``` +tests/ + conftest.py # shared fixtures (see §3.3) + unit/ # pure logic; no network, no real app, tmp_path only + integration/ # full FastAPI app over ASGI; real auth; real MemoryBackend + webhooks/ # webhook delivery pipeline (in-process receiver) + contract/ # spec + OpenAPI conformance + e2e/ # real subprocesses, real wire; marked `e2e` +``` + +### 3.2 Markers and defaults + +Registered in `pytest.ini`: + +| Marker | Meaning | In default run? | +|---|---|---| +| `unit` | pure logic | yes | +| `integration` | in-process app, real auth | yes | +| `contract` | spec/OpenAPI conformance | yes | +| `e2e` | spawns `mail-server`/`mail-daemon` subprocesses | no (`-m e2e` opt-in; runs in CI) | + +`addopts` gains `-m "not e2e"`; CI runs two jobs (default + e2e). +Keep `asyncio_mode = auto`. + +### 3.3 Shared fixtures (`tests/conftest.py`) + +The ~20-line `deployment_dir` fixture currently duplicated verbatim across +three files moves here, alongside: + +- `deployment_dir` — `tmp_path`-backed deployment tree; + monkeypatches `mail_server.backends.memory.fs.DEPLOYMENT_PATH` +- `backend` — started `MemoryBackend` seeded with a standard cast: + one admin, two users, one agent, one daemon, one swarm +- `app_client` — `TestClient` over the **real** `mail_server.server.app` + (env vars `MAIL_HOST`, `MAIL_JWT_SECRET_KEY`, `MAIL_JWT_ALGORITHM` set + before import), wired to `backend` +- `token_for(address)` — factory issuing real JWTs via `POST /auth/token`, + so integration tests exercise real auth instead of monkeypatching it +- `webhook_receiver` — in-process ASGI app that records deliveries and can be + told to fail N times (for retry-ladder tests) + +### 3.4 New dev dependencies + +- `respx` — `httpx` route mocking for `mail_client` / `mail_daemon` unit tests +- `schemathesis` *(optional, Phase 4)* — property-based fuzzing of endpoints + against `spec/openapi.yaml` + +Coverage: enable `pytest-cov` (already installed) scoped to the four v2 +packages; report in CI. Start with a visibility-only report; introduce a +ratchet threshold once Phase 2 lands. + +## 4. Test categories — scope definitions + +### Unit (`tests/unit/`) + +Pure functions and single classes; no app object, no sockets; filesystem only +via `tmp_path`. Owns: + +- all `mail_protocol` validators (full matrix: address grammars from SPEC §6, + uuid/subject/body/name/host bounds from `core/constants.py`) +- Pydantic model construction, validation edges, and `summarize()` for every + `mail_protocol.core` model (today only `lists` and `trash` are covered) +- `MemoryBackend` method-level behavior (agents/daemons/users/swarms/webhooks + CRUD, inbox/outbox/drafts/trash operations, buffer semantics) +- `memory.fs` load/save round-trips for every entity type (today: lists only) +- `mail_server.validators` (14 request-body validators → 422 paths) +- `mail_client.commands.*` and `mail_daemon.maild.api` against + `httpx.MockTransport`/`respx` (daemon tests must reset the module-level + `_mail_*` globals between tests — add an autouse fixture) +- existing CLI parser-shape tests (stay as-is) + +### Integration (`tests/integration/`) + +The real composed FastAPI app over ASGI, real JWT auth, real backend, no +subprocesses. Owns: + +- **Auth flows:** `POST /auth/token` (good/bad credentials), `whoami` per + role, password reset, expired/garbage tokens → 401 +- **Authorization boundaries:** user cannot read another user's + inbox/outbox/drafts/trash; non-admin → 403 on all `/admin/*`; daemon-only + endpoints reject user/admin tokens; agent-role behavior +- **Per-router behavior:** inbox, outbox, drafts (incl. `send`), trash, + swarms (+ health), admin (19 endpoints), daemon + (`message-buffer/clear`, `deliver/local`), lists (migrate existing endpoint + tests here, rewired to real auth) +- **Cross-endpoint flows in-process:** compose → send → buffer → deliver → + recipient inbox; list fan-out through the real routers +- **Webhook pipeline** (`integration/webhooks/`): delivery POST shape + (`WebhookDeliveredPostRequest`), HMAC-SHA256 signature verification + round-trip, retry ladder ordering (patch `asyncio.sleep`; assert the + 0s/1s/30s/5m/1h/6h schedule and give-up behavior), webhook CRUD effects on + delivery + +### Contract (`tests/contract/`) + +The spec is the oracle. Owns: + +- **OpenAPI drift check:** regenerate the schema exactly as + `scripts/generate_openapi.py` does and assert equality with the committed + `spec/openapi.yaml`. A failing check means: change the API deliberately and + regenerate, or revert. +- **SPEC.md conformance tests:** encode MUST/SHOULD clauses as tests that + reference their spec section in the test docstring — §6 address grammar + (host-scoped `user:`/`admin:`/`daemon:` forms, swarm-scoped agent and + `list:` forms), §7 message field requirements and bounds, §8 pre-send vs + post-send error semantics. Where the implementation and spec disagree, the + test fails and forces the conversation. +- **Schemathesis fuzzing** *(optional)*: generate requests from the OpenAPI + schema against the in-process app; assert no 500s and response-schema + conformance. + +### E2E (`tests/e2e/`) + +Real processes, real wire, few in number. A session-scoped fixture runs +`backend-init` into a tmp deployment, then launches `mail-server` (uvicorn) +and `mail-daemon` subprocesses with real env wiring, polling `/health` for +readiness. Tests drive the system through the `mail` / `mail-admin` CLIs: + +1. **Send/deliver journey:** login → compose → send → daemon delivers → + recipient sees the message via `inbox` / `inbox-open` +2. **List fan-out journey:** admin creates list → users subscribe → send to + `list:` address → all members receive +3. **Persistence across restart:** send/deliver → stop server cleanly → + relaunch on same deployment dir → inbox/outbox/lists intact +4. **Auth journey:** login, whoami, bad-password rejection, admin panel access + +These are the only tests that can catch env-var wiring, `backend_init` +provisioning, daemon global state, and shutdown persistence in combination. + +## 5. Execution phases + +Each phase is independently mergeable and leaves the default suite green. + +### Phase 0 — Foundation (small) +- Resolve the failing `test_subscribe_other_rejected_with_403` per the §7 + decision. +- Create `tests/conftest.py`; deduplicate the `deployment_dir` fixture out of + the three files that copy it. +- Create the category directories, register markers, update `pytest.ini` + (`-m "not e2e"`), move `tests/unit/` content as needed (no test rewrites). +- Wire `pytest-cov` reporting; gitignore `pytest.log`. +- Add `respx` as a dev dependency. + +**Exit:** suite green; one shared fixture set; coverage number visible. + +### Phase 1 — Integration: auth + routers (largest single phase) +- `app_client` + `token_for` fixtures (real app, real JWTs). +- Auth flow and authorization-boundary tests. +- Per-router endpoint tests for inbox, outbox, drafts, trash, swarms, admin, + daemon; migrate lists endpoint tests onto real auth. +- `xfail(raises=NotImplementedError)` tests for the known stubs + (`delete_inbox_message`, `delete_draft`, `delete_trash_message`, + `clear_trash`, `daemon_deliver_remote`, `admin_webhook_patch`) so the + checklist is executable. + +**Exit:** every registered route has ≥1 success and ≥1 authz/failure test; +auth layer no longer monkeypatched anywhere in integration tests. + +### Phase 2 — Webhook delivery pipeline +- `webhook_receiver` fixture; signature round-trip, payload shape, retry + ladder with patched sleep, give-up after final attempt, CRUD→delivery + effects. + +**Exit:** the six-commit bug cluster's behaviors are all pinned by tests. + +### Phase 3 — Contract layer +- OpenAPI drift check. +- SPEC.md §6/§7/§8 conformance tests with section-referencing docstrings. +- Decide on schemathesis adoption after evaluating runtime cost. + +**Exit:** an API change that isn't reflected in `spec/openapi.yaml` fails CI. + +### Phase 4 — Client + daemon units, protocol back-fill +- `mail_client.commands.*` against mocked transport (request shape, token + header, output rendering incl. markdown path); `mail-admin` commands. +- `mail_daemon.maild.api`: loop iteration behavior, buffer-clear/deliver + calls, startup validation, token acquisition; globals-reset fixture. +- Back-fill `mail_protocol` validator matrix, model edges, `memory.fs` + round-trips for all entity types, `mail_server.validators`. + +**Exit:** every v2 package has meaningful unit coverage; set the initial +coverage ratchet. + +### Phase 5 — E2E journeys + CI +- Subprocess harness fixture; the four journeys in §4. +- CI: default job (unit+integration+contract, coverage report) and e2e job. + +**Exit:** full-system happy paths run on every PR. + +## 6. Conventions + +- New v2 features land with tests in the matching category; bug fixes land + with a regression test (the webhook cluster is the cautionary tale). +- Conformance tests cite their SPEC.md section; when implementation and spec + conflict, the spec is amended or the code fixed — never the test deleted + silently. +- Stubbed functionality gets an `xfail(raises=NotImplementedError)` test at + introduction time. +- Shared fixtures live in `tests/conftest.py`; category-specific ones in that + category's `conftest.py`. No copy-pasted fixtures. + +## 7. Open decisions + +1. **Subscribe-on-behalf semantics — RESOLVED 2026-06-12: option (a).** + Commit `570a340` removed the request body from + `POST /lists/{list}/subscribe`; the endpoint always subscribes the + authenticated caller. This is ratified: subscribing *another* user-agent + is an admin-only capability (via `/admin/lists/{list}/members`), so the + public endpoint stays body-less. The stale 403 test is replaced by + `test_subscribe_ignores_supplied_member_address`. `spec/openapi.yaml` + already reflects the body-less endpoint; no spec change needed. +2. **Coverage ratchet level — RESOLVED 2026-06-12.** Set at Phase 4 exit: + `fail_under = 65` (suite measured 66%). Raise as coverage grows; never + lower. +3. **Schemathesis adoption — RESOLVED 2026-06-12: deferred.** The drift + check plus SPEC conformance tests cover the schema-shape ground; + revisit as a nightly CI job after Phase 5. + + +===== End of `docs/testing-plan.md` ===== + ===== `docs/README.md` ===== # MAIL v2 Documentation @@ -105,6 +384,11 @@ The old MAIL v1 reference implementation docs have been archived under `src/mail/legacy/docs/`. Use those archived docs only when maintaining or studying the legacy runtime. +## Repository-Level Docs + +- [`testing-plan.md`](testing-plan.md) - v2 testing suite overhaul plan + (categories, phases, conventions) + ## Active Package Docs - `src/mail/protocol/README.md` - protocol package documentation diff --git a/pyproject.toml b/pyproject.toml index e2eafdc4..0522b1b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,16 @@ [project] name = "mail-swarms" -version = "2.0.0" +version = "2.0.1" description = "Multi-Agent Interface Layer v2 workspace meta-package" readme = "README.md" requires-python = ">=3.12" dependencies = [ - "mail-client>=2.0.0", - "mail-server>=2.0.0", - "mail-daemon>=2.0.0", - "mail-protocol>=2.0.0", + # Lockstep meta-package: pin members to this exact release. Bump all five + # versions together (see scripts/bump-version or `uv version --package`). + "mail-swarms-client==2.0.1", + "mail-swarms-server==2.0.1", + "mail-swarms-daemon==2.0.1", + "mail-swarms-protocol==2.0.1", ] license = "Apache-2.0" classifiers = [ @@ -48,38 +50,26 @@ legacy = [ "fastmcp>=2.12.5", "asyncpg>=0.31.0", ] -protocol = ["mail-protocol>=2.0.0"] -server = ["mail-server>=2.0.0", "mail-protocol>=2.0.0"] -client = ["mail-client>=2.0.0", "mail-protocol>=2.0.0"] -daemon = ["mail-daemon>=2.0.0", "mail-protocol>=2.0.0"] +protocol = ["mail-swarms-protocol==2.0.1"] +server = ["mail-swarms-server==2.0.1", "mail-swarms-protocol==2.0.1"] +client = ["mail-swarms-client==2.0.1", "mail-swarms-protocol==2.0.1"] +daemon = ["mail-swarms-daemon==2.0.1", "mail-swarms-protocol==2.0.1"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" +# `mail-swarms` is a thin meta-package: it ships no code of its own, it only +# pins the four member distributions. The actual implementation lives in +# `mail-swarms-client`/`-server`/`-daemon`/`-protocol` (which still import as +# `mail_client`/`mail_server`/…), and the v1 `mail` / `mail.legacy` namespace is +# intentionally NOT published here. [tool.hatch.build.targets.wheel] -packages = ["src/mail"] +bypass-selection = true [tool.hatch.build.targets.sdist] -# Avoid slow sdist builds by skipping large, non-distribution directories. -exclude = [ - "ui/node_modules", - "ui/.next", - "ui/.turbo", - "src/mail/legacy/ui/node_modules", - "src/mail/legacy/ui/.next", - "src/mail/legacy/ui/.turbo", - "src/mail/legacy/ui/out", - "src/mail/legacy/ui/build", - ".venv", - "dist", - "logs", - ".mypy_cache", - ".ruff_cache", - ".pytest_cache", - ".coverage*", - "**/__pycache__", -] +# Keep the sdist minimal — no member source trees, no 1.5 GB legacy/ tree. +only-include = ["README.md"] [tool.mypy] ignore_missing_imports=true @@ -124,10 +114,10 @@ indent-style = "space" extend-select = ["TID252", "I", "UP"] [tool.uv.sources] -mail-protocol = { workspace = true } -mail-server = { workspace = true } -mail-client = { workspace = true } -mail-daemon = { workspace = true } +mail-swarms-protocol = { workspace = true } +mail-swarms-server = { workspace = true } +mail-swarms-client = { workspace = true } +mail-swarms-daemon = { workspace = true } [tool.uv.workspace] members = [ diff --git a/scripts/bump-version.py b/scripts/bump-version.py new file mode 100755 index 00000000..c8a3e5a3 --- /dev/null +++ b/scripts/bump-version.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +"""Bump every MAIL package to a new version, in lockstep. + +MAIL ships five distributions from one uv workspace that share a single +version and pin each other exactly (`mail-client==X`, ...). This script keeps +them in sync so a release can never go out half-bumped. + +Usage: + python scripts/bump-version.py 2.0.1 + python scripts/bump-version.py --check # verify all five already agree + +It rewrites, in every pyproject.toml: + * the package's own `version = "..."` + * any exact internal pin `"mail-==..."` +""" + +from __future__ import annotations + +import argparse +import pathlib +import re +import sys + +ROOT = pathlib.Path(__file__).resolve().parent.parent + +PYPROJECTS = [ + ROOT / "pyproject.toml", + ROOT / "src" / "mail" / "client" / "pyproject.toml", + ROOT / "src" / "mail" / "server" / "pyproject.toml", + ROOT / "src" / "mail" / "daemon" / "pyproject.toml", + ROOT / "src" / "mail" / "protocol" / "pyproject.toml", +] + +# PEP 440-ish: release plus optional pre/post/dev suffix (e.g. 2.0.1, 2.1.0rc1). +VERSION_RE = re.compile(r"^\d+\.\d+\.\d+(?:(?:a|b|rc)\d+|\.post\d+|\.dev\d+)?$") + +_VERSION_LINE = re.compile(r'^version = "[^"]+"$', re.MULTILINE) +# Any version specifier on an internal member pin is normalized to `==`. +# Scoped to the four member names, so external deps (e.g. dict2xml>=…) are untouched. +_INTERNAL_PIN = re.compile( + r'"(mail-swarms-(?:client|server|daemon|protocol))(?:==|>=|~=|!=|===|>|<)[^"]*"') + + +def current_version(text: str) -> str | None: + m = re.search(r'^version = "([^"]+)"$', text, re.MULTILINE) + return m.group(1) if m else None + + +def check() -> int: + versions = {p: current_version(p.read_text()) for p in PYPROJECTS} + unique = set(versions.values()) + for p, v in versions.items(): + print(f" {v}\t{p.relative_to(ROOT)}") + if len(unique) != 1 or None in unique: + print("ERROR: package versions are not in lockstep", file=sys.stderr) + return 1 + print(f"OK: all packages at {unique.pop()}") + return 0 + + +def bump(version: str) -> int: + if not VERSION_RE.match(version): + print(f"ERROR: '{version}' is not a valid version", file=sys.stderr) + return 2 + for p in PYPROJECTS: + text = p.read_text() + text = _VERSION_LINE.sub(f'version = "{version}"', text, count=1) + text = _INTERNAL_PIN.sub(rf'"\1=={version}"', text) + p.write_text(text) + print(f" bumped {p.relative_to(ROOT)} -> {version}") + print(f"\nAll packages set to {version}. Next: commit, tag v{version}, " + f"then publish the GitHub Release as a full (non-prerelease) release.") + return 0 + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + g = ap.add_mutually_exclusive_group(required=True) + g.add_argument("version", nargs="?", help="new version, e.g. 2.0.1") + g.add_argument("--check", action="store_true", + help="verify all packages already share one version") + args = ap.parse_args() + return check() if args.check else bump(args.version) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/mail/client/README.md b/src/mail/client/README.md index f9ed427c..55706d86 100644 --- a/src/mail/client/README.md +++ b/src/mail/client/README.md @@ -1,3 +1,3 @@ -# mail-client +# mail-swarms-client TODO diff --git a/src/mail/client/docs/README.md b/src/mail/client/docs/README.md index cd8c0370..d00d40a3 100644 --- a/src/mail/client/docs/README.md +++ b/src/mail/client/docs/README.md @@ -1,6 +1,6 @@ -# mail-client Documentation Index +# mail-swarms-client Documentation Index -This document is an index for all `mail-client` documentation. +This document is an index for all `mail-swarms-client` documentation. ## Tutorials diff --git a/src/mail/client/docs/reference/admin-panel.md b/src/mail/client/docs/reference/admin-panel.md index 3cc7e8b0..278c97c9 100644 --- a/src/mail/client/docs/reference/admin-panel.md +++ b/src/mail/client/docs/reference/admin-panel.md @@ -1,6 +1,6 @@ -# `mail-client` Admin Panel CLI Reference +# `mail-swarms-client` Admin Panel CLI Reference -This document serves as a reference to the `mail-client` administrator panel CLI. +This document serves as a reference to the `mail-swarms-client` administrator panel CLI. ## Usage diff --git a/src/mail/client/docs/reference/cli.md b/src/mail/client/docs/reference/cli.md index 08b63530..5aca9f08 100644 --- a/src/mail/client/docs/reference/cli.md +++ b/src/mail/client/docs/reference/cli.md @@ -1,6 +1,6 @@ -# `mail-client` CLI Reference +# `mail-swarms-client` CLI Reference -This document serves as a reference to the `mail-client` CLI. +This document serves as a reference to the `mail-swarms-client` CLI. ## Usage diff --git a/src/mail/client/docs/tutorials/quickstart.md b/src/mail/client/docs/tutorials/quickstart.md index 9e8c458b..13d14170 100644 --- a/src/mail/client/docs/tutorials/quickstart.md +++ b/src/mail/client/docs/tutorials/quickstart.md @@ -1,6 +1,6 @@ -# `mail-client` Quickstart Guide +# `mail-swarms-client` Quickstart Guide -This document serves as a tutorial on how to get started with `mail-client`. +This document serves as a tutorial on how to get started with `mail-swarms-client`. ## Prerequesites @@ -81,5 +81,5 @@ This includes the message's unique ID. ## See Also -- `mail-client` CLI reference: [reference/cli.md](/docs/reference/cli.md) -- `mail-client` admin panel CLI refernece: [reference/admin-panel.md](/docs/reference/admin-panel.md) +- `mail-swarms-client` CLI reference: [reference/cli.md](/docs/reference/cli.md) +- `mail-swarms-client` admin panel CLI refernece: [reference/admin-panel.md](/docs/reference/admin-panel.md) diff --git a/src/mail/client/pyproject.toml b/src/mail/client/pyproject.toml index 175631ba..188b6385 100644 --- a/src/mail/client/pyproject.toml +++ b/src/mail/client/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "mail-client" -version = "2.0.0" +name = "mail-swarms-client" +version = "2.0.1" description = "CLI client for interacting with MAIL servers" readme = "README.md" authors = [ @@ -10,7 +10,7 @@ requires-python = ">=3.12" dependencies = [ "httpx>=0.28.1", "pydantic>=2.11.7", - "mail-protocol>=2.0.0", + "mail-swarms-protocol==2.0.1", "rich>=14.1.0", "dotenv>=0.9.9", ] diff --git a/src/mail/daemon/pyproject.toml b/src/mail/daemon/pyproject.toml index d7ccff5a..40cb606b 100644 --- a/src/mail/daemon/pyproject.toml +++ b/src/mail/daemon/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "mail-daemon" -version = "2.0.0" +name = "mail-swarms-daemon" +version = "2.0.1" description = "Daemon implementation for the Multi-Agent Interface Layer (MAIL)" readme = "README.md" authors = [ @@ -9,7 +9,7 @@ authors = [ requires-python = ">=3.12" dependencies = [ "httpx>=0.28.1", - "mail-protocol>=2.0.0", + "mail-swarms-protocol==2.0.1", "pydantic>=2.11.7", ] @@ -24,4 +24,4 @@ build-backend = "hatchling.build" packages = ["src/mail_daemon"] [tool.uv.sources] -mail-protocol = { workspace = true } +mail-swarms-protocol = { workspace = true } diff --git a/src/mail/protocol/pyproject.toml b/src/mail/protocol/pyproject.toml index 99449ff2..42748519 100644 --- a/src/mail/protocol/pyproject.toml +++ b/src/mail/protocol/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "mail-protocol" -version = "2.0.0" +name = "mail-swarms-protocol" +version = "2.0.1" description = "Types and constants for the Multi-Agent Interface Layer (MAIL) protocol" readme = "README.md" authors = [ diff --git a/src/mail/server/README.md b/src/mail/server/README.md index b78601d3..9cd93092 100644 --- a/src/mail/server/README.md +++ b/src/mail/server/README.md @@ -1,3 +1,3 @@ -# mail-server +# mail-swarms-server TODO diff --git a/src/mail/server/docs/README.md b/src/mail/server/docs/README.md index e5b01596..f1d46bb0 100644 --- a/src/mail/server/docs/README.md +++ b/src/mail/server/docs/README.md @@ -1,10 +1,10 @@ -# `mail-server` Documentation Index +# `mail-swarms-server` Documentation Index -This document serves as the root documentation file for the `mail-server` Python package. +This document serves as the root documentation file for the `mail-swarms-server` Python package. ## Tutorials -- **`mail-server` quickstart**: [tutorials/quickstart.md](tutorials/quickstart.md) +- **`mail-swarms-server` quickstart**: [tutorials/quickstart.md](tutorials/quickstart.md) ## Reference Docs diff --git a/src/mail/server/docs/tutorials/quickstart.md b/src/mail/server/docs/tutorials/quickstart.md index 8d6056c3..c3315853 100644 --- a/src/mail/server/docs/tutorials/quickstart.md +++ b/src/mail/server/docs/tutorials/quickstart.md @@ -1,6 +1,6 @@ -# `mail-server` Quickstart Guide +# `mail-swarms-server` Quickstart Guide -This document serves as a tutorial on how to get started with `mail-server`. +This document serves as a tutorial on how to get started with `mail-swarms-server`. ## Prerequesites diff --git a/src/mail/server/pyproject.toml b/src/mail/server/pyproject.toml index dc8c644b..46c65bfb 100644 --- a/src/mail/server/pyproject.toml +++ b/src/mail/server/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "mail-server" -version = "2.0.0" +name = "mail-swarms-server" +version = "2.0.1" description = "FastAPI server for the Multi-Agent Interface Layer (MAIL)" readme = "README.md" authors = [ @@ -10,7 +10,7 @@ requires-python = ">=3.12" dependencies = [ "aiohttp>=3.12.15", "fastapi>=0.116.1", - "mail-protocol>=2.0.0", + "mail-swarms-protocol==2.0.1", "pwdlib[argon2]>=0.3.0", "pydantic>=2.11.7", "pyjwt>=2.10.1", @@ -31,4 +31,4 @@ build-backend = "hatchling.build" packages = ["src/mail_server"] [tool.uv.sources] -mail-protocol = { workspace = true } +mail-swarms-protocol = { workspace = true } diff --git a/uv.lock b/uv.lock index 866ba6f5..97b401fc 100644 --- a/uv.lock +++ b/uv.lock @@ -8,11 +8,11 @@ resolution-markers = [ [manifest] members = [ - "mail-client", - "mail-daemon", - "mail-protocol", - "mail-server", "mail-swarms", + "mail-swarms-client", + "mail-swarms-daemon", + "mail-swarms-protocol", + "mail-swarms-server", ] [[package]] @@ -1249,107 +1249,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/d9/5f8ed27241b487f51f04573b8ba06d4460ebed9f792ff5cc148649fbf862/litellm-1.76.3-py3-none-any.whl", hash = "sha256:d62e3ff2a80ec5e551c6d7a0fe199ffe718ecb6cbaa43fc9250dd8d7c0944352", size = 9000797, upload-time = "2025-09-07T01:59:16.261Z" }, ] -[[package]] -name = "mail-client" -version = "2.0.0" -source = { editable = "src/mail/client" } -dependencies = [ - { name = "dotenv" }, - { name = "httpx" }, - { name = "mail-protocol" }, - { name = "pydantic" }, - { name = "rich" }, -] - -[package.metadata] -requires-dist = [ - { name = "dotenv", specifier = ">=0.9.9" }, - { name = "httpx", specifier = ">=0.28.1" }, - { name = "mail-protocol", editable = "src/mail/protocol" }, - { name = "pydantic", specifier = ">=2.11.7" }, - { name = "rich", specifier = ">=14.1.0" }, -] - -[[package]] -name = "mail-daemon" -version = "2.0.0" -source = { editable = "src/mail/daemon" } -dependencies = [ - { name = "httpx" }, - { name = "mail-protocol" }, - { name = "pydantic" }, -] - -[package.metadata] -requires-dist = [ - { name = "httpx", specifier = ">=0.28.1" }, - { name = "mail-protocol", editable = "src/mail/protocol" }, - { name = "pydantic", specifier = ">=2.11.7" }, -] - -[[package]] -name = "mail-protocol" -version = "2.0.0" -source = { editable = "src/mail/protocol" } -dependencies = [ - { name = "pydantic" }, - { name = "validators" }, -] - -[package.metadata] -requires-dist = [ - { name = "pydantic", specifier = ">=2.11.7" }, - { name = "validators", specifier = ">=0.35.0" }, -] - -[[package]] -name = "mail-server" -version = "2.0.0" -source = { editable = "src/mail/server" } -dependencies = [ - { name = "aiohttp" }, - { name = "fastapi" }, - { name = "mail-protocol" }, - { name = "pwdlib", extra = ["argon2"] }, - { name = "pydantic" }, - { name = "pyjwt" }, - { name = "python-dotenv" }, - { name = "python-multipart" }, - { name = "uvicorn" }, -] - -[package.metadata] -requires-dist = [ - { name = "aiohttp", specifier = ">=3.12.15" }, - { name = "fastapi", specifier = ">=0.116.1" }, - { name = "mail-protocol", editable = "src/mail/protocol" }, - { name = "pwdlib", extras = ["argon2"], specifier = ">=0.3.0" }, - { name = "pydantic", specifier = ">=2.11.7" }, - { name = "pyjwt", specifier = ">=2.10.1" }, - { name = "python-dotenv", specifier = ">=1.1.1" }, - { name = "python-multipart", specifier = ">=0.0.20" }, - { name = "uvicorn", specifier = ">=0.35.0" }, -] - [[package]] name = "mail-swarms" -version = "2.0.0" +version = "2.0.1" source = { editable = "." } dependencies = [ - { name = "mail-client" }, - { name = "mail-daemon" }, - { name = "mail-protocol" }, - { name = "mail-server" }, + { name = "mail-swarms-client" }, + { name = "mail-swarms-daemon" }, + { name = "mail-swarms-protocol" }, + { name = "mail-swarms-server" }, ] [package.optional-dependencies] client = [ - { name = "mail-client" }, - { name = "mail-protocol" }, + { name = "mail-swarms-client" }, + { name = "mail-swarms-protocol" }, ] daemon = [ - { name = "mail-daemon" }, - { name = "mail-protocol" }, + { name = "mail-swarms-daemon" }, + { name = "mail-swarms-protocol" }, ] legacy = [ { name = "aiohttp" }, @@ -1376,11 +1294,11 @@ legacy = [ { name = "uvicorn" }, ] protocol = [ - { name = "mail-protocol" }, + { name = "mail-swarms-protocol" }, ] server = [ - { name = "mail-protocol" }, - { name = "mail-server" }, + { name = "mail-swarms-protocol" }, + { name = "mail-swarms-server" }, ] [package.dev-dependencies] @@ -1408,17 +1326,17 @@ requires-dist = [ { name = "langgraph", marker = "extra == 'legacy'", specifier = ">=0.6.3" }, { name = "langmem", marker = "extra == 'legacy'", specifier = ">=0.0.29" }, { name = "litellm", marker = "extra == 'legacy'", specifier = ">=1.76.2" }, - { name = "mail-client", editable = "src/mail/client" }, - { name = "mail-client", marker = "extra == 'client'", editable = "src/mail/client" }, - { name = "mail-daemon", editable = "src/mail/daemon" }, - { name = "mail-daemon", marker = "extra == 'daemon'", editable = "src/mail/daemon" }, - { name = "mail-protocol", editable = "src/mail/protocol" }, - { name = "mail-protocol", marker = "extra == 'client'", editable = "src/mail/protocol" }, - { name = "mail-protocol", marker = "extra == 'daemon'", editable = "src/mail/protocol" }, - { name = "mail-protocol", marker = "extra == 'protocol'", editable = "src/mail/protocol" }, - { name = "mail-protocol", marker = "extra == 'server'", editable = "src/mail/protocol" }, - { name = "mail-server", editable = "src/mail/server" }, - { name = "mail-server", marker = "extra == 'server'", editable = "src/mail/server" }, + { name = "mail-swarms-client", editable = "src/mail/client" }, + { name = "mail-swarms-client", marker = "extra == 'client'", editable = "src/mail/client" }, + { name = "mail-swarms-daemon", editable = "src/mail/daemon" }, + { name = "mail-swarms-daemon", marker = "extra == 'daemon'", editable = "src/mail/daemon" }, + { name = "mail-swarms-protocol", editable = "src/mail/protocol" }, + { name = "mail-swarms-protocol", marker = "extra == 'client'", editable = "src/mail/protocol" }, + { name = "mail-swarms-protocol", marker = "extra == 'daemon'", editable = "src/mail/protocol" }, + { name = "mail-swarms-protocol", marker = "extra == 'protocol'", editable = "src/mail/protocol" }, + { name = "mail-swarms-protocol", marker = "extra == 'server'", editable = "src/mail/protocol" }, + { name = "mail-swarms-server", editable = "src/mail/server" }, + { name = "mail-swarms-server", marker = "extra == 'server'", editable = "src/mail/server" }, { name = "numpydoc", marker = "extra == 'legacy'", specifier = ">=1.9.0" }, { name = "openai", marker = "extra == 'legacy'", specifier = ">=1.106.1" }, { name = "pydantic", marker = "extra == 'legacy'", specifier = ">=2.11.7" }, @@ -1447,6 +1365,88 @@ dev = [ { name = "tqdm", specifier = ">=4.67.1" }, ] +[[package]] +name = "mail-swarms-client" +version = "2.0.1" +source = { editable = "src/mail/client" } +dependencies = [ + { name = "dotenv" }, + { name = "httpx" }, + { name = "mail-swarms-protocol" }, + { name = "pydantic" }, + { name = "rich" }, +] + +[package.metadata] +requires-dist = [ + { name = "dotenv", specifier = ">=0.9.9" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "mail-swarms-protocol", editable = "src/mail/protocol" }, + { name = "pydantic", specifier = ">=2.11.7" }, + { name = "rich", specifier = ">=14.1.0" }, +] + +[[package]] +name = "mail-swarms-daemon" +version = "2.0.1" +source = { editable = "src/mail/daemon" } +dependencies = [ + { name = "httpx" }, + { name = "mail-swarms-protocol" }, + { name = "pydantic" }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx", specifier = ">=0.28.1" }, + { name = "mail-swarms-protocol", editable = "src/mail/protocol" }, + { name = "pydantic", specifier = ">=2.11.7" }, +] + +[[package]] +name = "mail-swarms-protocol" +version = "2.0.1" +source = { editable = "src/mail/protocol" } +dependencies = [ + { name = "pydantic" }, + { name = "validators" }, +] + +[package.metadata] +requires-dist = [ + { name = "pydantic", specifier = ">=2.11.7" }, + { name = "validators", specifier = ">=0.35.0" }, +] + +[[package]] +name = "mail-swarms-server" +version = "2.0.1" +source = { editable = "src/mail/server" } +dependencies = [ + { name = "aiohttp" }, + { name = "fastapi" }, + { name = "mail-swarms-protocol" }, + { name = "pwdlib", extra = ["argon2"] }, + { name = "pydantic" }, + { name = "pyjwt" }, + { name = "python-dotenv" }, + { name = "python-multipart" }, + { name = "uvicorn" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiohttp", specifier = ">=3.12.15" }, + { name = "fastapi", specifier = ">=0.116.1" }, + { name = "mail-swarms-protocol", editable = "src/mail/protocol" }, + { name = "pwdlib", extras = ["argon2"], specifier = ">=0.3.0" }, + { name = "pydantic", specifier = ">=2.11.7" }, + { name = "pyjwt", specifier = ">=2.10.1" }, + { name = "python-dotenv", specifier = ">=1.1.1" }, + { name = "python-multipart", specifier = ">=0.0.20" }, + { name = "uvicorn", specifier = ">=0.35.0" }, +] + [[package]] name = "markdown-it-py" version = "4.0.0" From 3ffa10134fbdc5f9f8481bd7b8c06788e4091d18 Mon Sep 17 00:00:00 2001 From: Addison Kline <77369109+addisonkline@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:28:23 -0400 Subject: [PATCH 2/2] fix: server utils now checks pkg meta for `mail-swarms-server` --- src/mail/server/src/mail_server/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mail/server/src/mail_server/utils.py b/src/mail/server/src/mail_server/utils.py index 3ca2ea07..ec5e5a50 100644 --- a/src/mail/server/src/mail_server/utils.py +++ b/src/mail/server/src/mail_server/utils.py @@ -27,16 +27,16 @@ def build_box_metadata( def get_mail_server_version() -> str: """ - Get the version of `mail-server` from package metadata. + Get the version of `mail-swarms-server` from package metadata. """ - version = metadata.version("mail-server") + version = metadata.version("mail-swarms-server") return version def get_mail_protocol_version() -> str: """ - Get the version of the MAIL protocol being used by `mail-server`. + Get the version of the MAIL protocol being used by `mail-swarms-server`. """ mail_server_v = get_mail_server_version()