Skip to content

MAIL v2: documentation overhaul (+ accumulated v2 feature line) - #85

Merged
addisonkline merged 32 commits into
mainfrom
kline/v2-docs
Jul 1, 2026
Merged

MAIL v2: documentation overhaul (+ accumulated v2 feature line)#85
addisonkline merged 32 commits into
mainfrom
kline/v2-docs

Conversation

@addisonkline

Copy link
Copy Markdown
Collaborator

Lands the kline/v2-docs branch into main: the accumulated MAIL v2 feature work plus a full documentation overhaul. 31 commits total.

⚠️ One source change in the documentation pass

Aside from the pre-existing feature commits below, the docs pass itself is documentation-only except for one deliberate change:

  • 750a2a3 — align identifier length cap to the spec (31 → 32). mail_protocol/core/constants.py hard-capped agent/user/admin/daemon-worker/swarm/list identifiers at 31, while SPEC §6 says they SHOULD be ≤ 32 (message tags were already 32). Raised the six *_LEN_MAX constants to 32 so the implementation matches the spec. Behavior change: identifiers of exactly 32 characters are now accepted. Length tests are constant-relative, so they follow automatically; full suite green.

Documentation overhaul (this pass)

  • P0 — unblock: resolved 18 unresolved merge-conflict markers the rebase left across 8 doc files (6e8b09c).
  • P1 — drift fixes: corrected 7 mismatches in already-written docs — swarms-listswarm-list, the list-patch NotImplementedError stub, list address forms, the mail refresh flow, and the HTTP-client token/message payloads (9d357dd).
  • P2 — wrote every remaining stub: 10 reference pages (incl. 4 CLI references auto-generated from argparse via the new scripts/build_cli_docs.py), 5 explanations, 6 how-tos.
  • P3 — index/hygiene: fixed docs/README.md omissions and finished initialize-memory-backend (d0a7fd3).
  • Accuracy correction: documented that six operations (delete inbox/draft/trash, trash-clear, webhook-patch, remote-deliver) are NotImplementedError on the memory backend and implemented only on SQLite (9b72cb5).
  • Root README rewritten as a proper GitHub landing page (3fa6025).

Result: docs/ now has zero stubs; all relative links verified.

Accumulated v2 features (already on the branch, ahead of main)

  • Refresh-token support (de3b37e)
  • Per-owner inbox read/unread status (6c595f9)
  • Message replies + tags (1e400da) and forwarding (1f9b3d0)
  • Draft editing + file-sourced draft bodies (447a9ec)
  • Standardized admin endpoint address path params (038af01)
  • OpenAPI request-body + box query-param documentation (e1aac76)
  • CLI copyright rebrand + --license (bb5e5b3)
  • Plus the earlier v2 docs work (webhook delivery, mailing lists, tutorials)

Testing

uv run pytest769 passed, 1 skipped, 6 xfailed (the xfails are the documented memory-backend stubs).

Documented follow-ups (not changed here)

  • list-patch CLI verb raises NotImplementedError — documented rather than implemented.
  • The six memory-backend stub operations above — implemented on SQLite, documented for memory.

🤖 Generated with Claude Code

addisonkline and others added 30 commits June 16, 2026 14:39
…bhook-receiver tutorial

Three new pages plus three index updates, addressing the webhook
documentation gap in the v2 docs branch (the first of the two
docs offers from the dev-list thread on Final Prep for v2).

## docs/explanations/webhook-delivery.md (new)

The conceptual contract. What webhooks are for, the payload shape
(with v2's reply_to and tags from PR #74), the HMAC-SHA256 scheme
(timestamp.body, not body.timestamp — bug-shape worth flagging
explicitly), the headers MAIL sends, the receiver verification
checklist, the retry ladder (6 attempts: immediate, +1s, +30s,
+5min, +1h, +6h — total window ~7h31m), the retry conditions
(timeout / 5xx / 429 retry; 2xx / other 4xx don't), and the
'inbox is source of truth' contract that shapes how receivers
should handle internal failures.

Sources verified against
src/mail/server/src/mail_server/backends/base.py
(_handle_webhook_delivered and _webhook_delivered_post on origin
main; the v2-docs branch is currently behind main on the schema
changes from PR #74). Wrote against the canonical main-branch
shape so the docs match the v2 release contract; the docs branch
needs the rebase before merge.

## docs/howtos/manage-webhooks.md (new)

Operator's guide. Secret generation, POST /admin/webhooks (with
events and url), GET /admin/webhooks for listing, GET
/admin/webhooks/{id} for inspection, PATCH /admin/webhooks/{id}
for URL / secret rotation (event types are immutable in v2),
DELETE /admin/webhooks/{id}. Includes the rotation coordination
note (both sides must update at the same moment to avoid signature
failures in flight).

## docs/tutorials/build-webhook-receiver.md (new)

Implementer's walkthrough. A single-file FastAPI receiver with:

- verify_signature using HMAC over raw bytes (calls out the two
  most common bugs: re-encoded JSON breaking signature; missing
  the timestamp.body prefix).
- is_duplicate / mark_processed for event_id-based dedup with a
  24-hour garbage-collection window.
- is_timestamp_in_window for 5-min skew rejection.
- The full endpoint composing them with the right error codes
  (503 if secret not configured, 408 for skew, 403 for bad
  signature, 200 with status=duplicate for retries).
- Registration command for end-to-end test.
- Diagnostic checklist for the 'nothing arrives' case.

## Index updates

docs/{explanations,howtos,tutorials}/README.md each gain a row
linking to the new page.

## Followups in scope of the original offer

- docs/howtos/manage-mailing-lists.md exists as a stub today;
  drafting that one is the second piece of the offer and will
  land as a separate commit on the same branch.
- docs/references/http-api.md is a stub overall (not just for
  webhooks). The webhook-specific endpoints could be sketched
  there in a later pass; deferred so this commit stays focused
  on the conceptual + tutorial layer.

## Verification

The four facts that are easiest to get wrong (and that I had
ground truth on from the chorus-side webhook receiver):

- HMAC inputs: 'timestamp.raw_body' not 'raw_body.timestamp'.
- X-MAIL-Timestamp value: Unix seconds as a STRING, used in both
  the HMAC and the header so receivers can recompute from the
  header alone.
- Signature header format: 'sha256=<hex>'.
- Body bytes: payload.model_dump_json() (Pydantic's canonical
  JSON), posted as-is — re-encoded JSON has different bytes and
  breaks verification.

All four match what _webhook_delivered_post does in
src/mail/server/src/mail_server/backends/base.py:653.
Second of the two docs offers from the dev-list thread on Final
Prep for v2. Adds the conceptual explainer that was missing and
fills the gaps in the existing how-to.

## docs/explanations/mailing-lists.md (new)

The conceptual model. Coverage:

- What a list is: a swarm-scoped, addressable fan-out target
  with no inbox.
- The address shape: list:<name>@<swarm>@<host>, with the list:
  prefix as the distinguishing marker against the agent / user
  / admin shapes.
- Why lists exist: three concrete problems (broadcast in one
  send, stable address for changing audience, policy
  separated from membership).
- Anatomy: name, swarm, host, owner, members, policy, metadata.
  Canonical address immutable; policy and members mutable.
- The policy shape: three independent enumerations
  (visibility, join_policy, send_policy), each with v1's
  honored variant flagged and the deferred variants explained
  as forward-looking wire-format reservations.
- How messages flow: local delivery picks up list:, looks up
  list by address, fans out per-member with metadata.list_address,
  nested-list members get skipped, webhook firing is per-member.
- Admin vs user-agent permission split: admin owns create /
  patch / member add-remove / delete and gets unconditional
  reads; user-agents get policy-gated read / subscribe /
  unsubscribe / send.
- Addressability examples (table of the four address shapes).
- Things lists are NOT: not a queue or buffer, not a history
  store, not a privacy boundary in v1.

Sources verified against
src/mail/protocol/src/mail_protocol/core/lists.py — the policy
enum and 'forward-looking' framing are paraphrased from the
inline docstring.

## docs/howtos/manage-mailing-lists.md (polished)

The existing how-to covered list / list-get / create / subscribe
/ unsubscribe / member-post / member-delete / send. Added:

- An updated Starting Point that points at the new explanation
  and names the address-shape convention explicitly.
- Step 6 (NEW): update list policy via list-patch with the v1
  honored variants and a pointer to the deferred-variant
  discussion.
- Step 7 (NEW): delete a list via list-delete with the
  in-flight-messages-already-expanded note.
- Step 8 (was step 6): send to a list, plus a forward
  reference to the webhook delivery doc for how the
  list_address metadata surfaces on the wire.
- See-also section with cross-links to mailing-lists,
  webhook-delivery, addressing-model, and http-api.

## docs/explanations/README.md

Index row added for the new mailing-lists explainer.
docs: webhook delivery contract, manage-webhooks how-to, and build-webhook-receiver tutorial
Signed-off-by: Addison Kline <77369109+addisonkline@users.noreply.github.com>
docs: mailing-lists explanation + manage-mailing-lists polish
Add MAIL 2.0 message-reply and tag support across the protocol, server,
and client, plus a migration path for existing deployments.

Protocol:
- MAILMessage gains required `mail_version` ("2.0") and `tags`, plus an
  optional `reply_to` referencing the replied-to message's id.
- Thread `reply_to`/`tags` through DraftPostRequest, MAILDraft, and
  `tags` through DraftSendPostRequest (all default-safe so pre-2.0
  drafts stay loadable).
- Surface `reply_to` (msg_-prefixed) and `tags` in MAILMessageInWebhook.

Server:
- post_draft stores reply_to/tags on the draft; send_draft stamps
  mail_version, copies reply_to, and merges draft + send-time tags as an
  order-preserving union.
- Webhook delivery payloads now carry reply_to/tags.

Client:
- New `reply` command (alias `r`): replies to the original sender,
  defaults the subject to `Re: <subject>`, sets reply_to.
- `--tags` on compose, send, and reply; reply_to/tags shown in message
  output and listed in `mail --help`.

Migration:
- scripts/migrate_messages_v2.py backfills mail_version/tags on persisted
  message records (dry-run, backup, deployment overrides; idempotent).

Tests/docs:
- Update all MAILMessage construction sites; add coverage for tag
  validators, the reply command, draft/tag-merge behavior, webhook
  payload fields, and the migration script.
- Document the new fields in SPEC.md (§7.8-7.10), regenerate
  spec/openapi.yaml, and update the client CLI reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Change the user-facing copyright in CLI --help footers and the
backend-init epilog from "Addison Kline" to "2025-present MAIL
Contributors". Add a shared --license flag (via cli_help) that prints
the Apache-2.0 notice and exits, wired into all CLIs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add two draft enhancements:

1. `mail compose` can now read a draft body from a file via
   `-F`/`--body-file PATH` as an alternative to the inline positional
   body. Exactly one of the two must be supplied.

2. A new `PATCH /drafts/{draft_id}` endpoint lets an authenticated
   user-agent update an existing draft's subject, body, reply_to, and
   tags. Only supplied fields change (tags: [] clears, omitted leaves
   unchanged) and updated_at is refreshed on any edit. Exposed in the
   CLI as `mail draft-edit` (alias `de`).

Regenerates spec/openapi.yaml and updates the HTTP and CLI docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Admin (and list) endpoints addressed resources by three inconsistent
slices of the canonical MAIL address: agents used a partial address
(name@swarm), daemons/users a bare id, and lists the full
list:name@swarm@host address. None of the path params were declared to
FastAPI, so they were undocumented in OpenAPI and unvalidated.

Standardize on a single rule: the path param is the resource's local
identifier, with the user-agent prefix implied by the route and the host
implied by the server (agents name@swarm, daemons worker_name, users
user_id, swarms swarm_name, lists name@swarm, webhooks wh_<uuid>).
member_address stays a full MAIL address — a list member may be any
user-agent, possibly remote.

- Add typed Path(...) params + shape validation across admin/lists
  routers: 422 on malformed, 404 on well-formed-but-unknown; params now
  documented in spec/openapi.yaml.
- Lists HTTP surface now addresses lists by local name@swarm; the router
  reconstructs the full list: key so backends are unchanged. Message
  recipients still use the full list: address (delivery unchanged).
- Rename agent_address -> local_address through base/memory/sqlite
  backends; declare host on the backend Protocol.
- Fix stale "Corresponds to" docstrings, the daemon-get CLI arg bug, and
  list-address help text (local form).
- Update tests + fixtures to local addresses; add 422 path-param tests.

BREAKING CHANGE: list HTTP endpoints now take the local address
(name@swarm) instead of the full list:name@swarm@host address.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a stateful, rotating refresh-token mechanism so browsers (and other
long-lived clients) can renew their access-token JWT without re-entering a
password.

Refresh tokens are opaque, high-entropy strings stored hashed (sha256) and
grouped into families: login starts a family, each rotation keeps the family
and carries its absolute `expires_at` forward unchanged (no sliding window).
Presenting a revoked-or-rotated token is treated as reuse and revokes the whole
family. Only interactive principals (users/admins) get refresh tokens; agents
and daemons re-authenticate with their credentials.

Delivery is dual: the token is returned in the response body and set as an
`httpOnly; Secure; SameSite=Strict` cookie scoped to `/auth`, so browsers get
silent renewal while the wider API stays header-only and CSRF-immune; the CLI
sends the token back in the request body.

- protocol: `RefreshTokenRecord`; `refresh_token`/`expires_in` on the token
  response; `AuthRefreshPostRequest`/`AuthRefreshPostResponse`/`AuthLogoutPostResponse`
- backends: six refresh-token methods on the protocol, implemented for both the
  memory (persisted via fs checkpoints) and sqlite (new `refresh_tokens` table,
  FK cascade on user deletion) backends, with dual-backend conformance tests
- server: `/auth/token` mints + sets the cookie, new `/auth/refresh` (rotate +
  reuse detection + fail-closed on deleted owner) and `/auth/logout`, and
  `/auth/password/reset` now revokes all of the principal's families
- config: env-only `MAIL_REFRESH_TOKEN_EXPIRE_DAYS` (required),
  `MAIL_COOKIE_SECURE`, `MAIL_COOKIE_DOMAIN`
- client: `mail refresh` command; `mail login` surfaces the refresh token
- docs: HTTP API reference, quickstarts, .env.example, regenerated openapi.yaml

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST/PATCH handlers took only `request: Request` and parsed bodies by hand
via validators.py, so FastAPI generated no requestBody schema and the bodies
were absent from /docs and spec/openapi.yaml. Promote each request model to a
typed handler parameter (Approach A) across the drafts, daemon, admin, lists,
and auth routers; FastAPI now validates and documents all 17 bodies.

/auth/refresh and /auth/logout take the refresh-token body as an optional
Body(default=None) to preserve the cookie-only browser flow; _read_refresh_token
is now sync and reads the parsed payload, keeping cookie-over-body precedence.

Also declare BoxFilterParams as a typed Query() param on the inbox/outbox/
trash/drafts GET endpoints (same root cause, query side), so the limit/offset/
sort_by/order filters are documented too.

Drop the now-unused body and query validators (path-param validators kept).
Add the missing MAIL_REFRESH_TOKEN_EXPIRE_DAYS placeholder to
generate_openapi.py so the spec regenerates standalone, and regenerate
spec/openapi.yaml. Add a contract test asserting requestBody/query-param
presence so the regression can't silently return.

The 422 body shape for invalid bodies is now FastAPI's native structured
detail instead of the previous string; status code is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Messages are delivered unread and marked read when the owning user-agent
opens them via GET /inbox/{message_id}. Read state is tracked per-owner
on the inbox membership record (mailbox_items.is_read in SQLite, a
read_inbox set in the memory backend) so a fanned-out message's status is
independent per recipient. Surfaced as is_read on MAILInboxEntrySummary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bhook-receiver tutorial

Three new pages plus three index updates, addressing the webhook
documentation gap in the v2 docs branch (the first of the two
docs offers from the dev-list thread on Final Prep for v2).

The conceptual contract. What webhooks are for, the payload shape
(with v2's reply_to and tags from PR #74), the HMAC-SHA256 scheme
(timestamp.body, not body.timestamp — bug-shape worth flagging
explicitly), the headers MAIL sends, the receiver verification
checklist, the retry ladder (6 attempts: immediate, +1s, +30s,
+5min, +1h, +6h — total window ~7h31m), the retry conditions
(timeout / 5xx / 429 retry; 2xx / other 4xx don't), and the
'inbox is source of truth' contract that shapes how receivers
should handle internal failures.

Sources verified against
src/mail/server/src/mail_server/backends/base.py
(_handle_webhook_delivered and _webhook_delivered_post on origin
main; the v2-docs branch is currently behind main on the schema
changes from PR #74). Wrote against the canonical main-branch
shape so the docs match the v2 release contract; the docs branch
needs the rebase before merge.

Operator's guide. Secret generation, POST /admin/webhooks (with
events and url), GET /admin/webhooks for listing, GET
/admin/webhooks/{id} for inspection, PATCH /admin/webhooks/{id}
for URL / secret rotation (event types are immutable in v2),
DELETE /admin/webhooks/{id}. Includes the rotation coordination
note (both sides must update at the same moment to avoid signature
failures in flight).

Implementer's walkthrough. A single-file FastAPI receiver with:

- verify_signature using HMAC over raw bytes (calls out the two
  most common bugs: re-encoded JSON breaking signature; missing
  the timestamp.body prefix).
- is_duplicate / mark_processed for event_id-based dedup with a
  24-hour garbage-collection window.
- is_timestamp_in_window for 5-min skew rejection.
- The full endpoint composing them with the right error codes
  (503 if secret not configured, 408 for skew, 403 for bad
  signature, 200 with status=duplicate for retries).
- Registration command for end-to-end test.
- Diagnostic checklist for the 'nothing arrives' case.

docs/{explanations,howtos,tutorials}/README.md each gain a row
linking to the new page.

- docs/howtos/manage-mailing-lists.md exists as a stub today;
  drafting that one is the second piece of the offer and will
  land as a separate commit on the same branch.
- docs/references/http-api.md is a stub overall (not just for
  webhooks). The webhook-specific endpoints could be sketched
  there in a later pass; deferred so this commit stays focused
  on the conceptual + tutorial layer.

The four facts that are easiest to get wrong (and that I had
ground truth on from the chorus-side webhook receiver):

- HMAC inputs: 'timestamp.raw_body' not 'raw_body.timestamp'.
- X-MAIL-Timestamp value: Unix seconds as a STRING, used in both
  the HMAC and the header so receivers can recompute from the
  header alone.
- Signature header format: 'sha256=<hex>'.
- Body bytes: payload.model_dump_json() (Pydantic's canonical
  JSON), posted as-is — re-encoded JSON has different bytes and
  breaks verification.

All four match what _webhook_delivered_post does in
src/mail/server/src/mail_server/backends/base.py:653.
The v2-docs rebase (9da55cd) committed unresolved conflict markers into
eight doc files. Resolve each by keeping the finished (HEAD) content and
dropping the leftover pre-writing scaffolding (Draft Outline / Steps to
Cover / Validation) and duplicated Not Here blocks. No written content is
lost; the run-local-mail tutorial body was wrapped inside a HEAD block.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix seven concrete mismatches found cross-checking docs against source:

- manage-swarms: command is 'swarm-list', not 'swarms-list'
- manage-mailing-lists: 'list-patch' CLI verb is a stub that raises
  NotImplementedError; document the PATCH /admin/lists endpoint instead
- manage-mailing-lists: management commands take the local name@swarm
  form, only sends use the full list:name@swarm@host recipient form
- authenticate-user-agent: document the mail refresh / MAIL_REFRESH_TOKEN
  flow for interactive principals instead of re-login only
- addressing-model: identifier hard cap is 31, not 32
- delivery-model: /daemon/deliver/remote route is wired but its handler
  raises NotImplementedError; say 'not yet functional', not 'not implemented'
- build-minimal-http-client: token response includes refresh_token and
  expires_in; send response includes mail_version, reply_to, and tags

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- docs/README.md 'Proposed Layout' now lists the pages that already exist:
  Build a Webhook Receiver (tutorials), Manage Webhooks (how-tos), and
  Mailing Lists + Webhook Delivery (explanations).
- initialize-memory-backend: write the previously-TODO 'reinitialize a
  clean slate' step (delete the deployment dir under ~/.mail-swarms and
  re-run backend-init) and flip its stale 'Status: stub' to 'draft'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fill in six reference stubs against verified source:
- repository-layout: workspace/package map, scripts, tests, legacy
- protocol-specification: SPEC.md + openapi roles, section map, contract
  tests, and the known spec-vs-impl identifier-length divergence (31 vs 32)
- http-api: full route inventory with auth levels + status codes
- data-models: protocol model field tables, constants, validators
- storage-backends: backend contract, memory vs sqlite, backend-init
- configuration: exhaustive env var + CLI flag inventory

Also refine addressing-model to distinguish the spec's SHOULD-32 from the
reference implementation's hard cap of 31.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add scripts/build_cli_docs.py, which imports each CLI's build_parser() and
renders a Markdown reference (global options + subcommands with their args,
aliases, and expanded defaults). Generate the four CLI reference pages
(client/admin/server/daemon) from it so they cannot drift from the parsers.
Regenerate with: uv run python scripts/build_cli_docs.py

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SPEC.md §6 says agent/user/admin/daemon-worker/swarm/list identifiers
SHOULD be <= 32 chars, but the reference implementation hard-capped at 31.
Raise all name/keyword *_LEN_MAX constants from 31 to 32 so the impl matches
the spec (message tags were already 32). Contract/unit length tests are
constant-relative, so they follow automatically; full suite green
(769 passed). Update the docs that had flagged the divergence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
While verifying the 31->32 change against the full suite, the xfail'd
tests/integration/test_stubs.py surfaced that six operations are
NotImplementedError on the *memory* backend only and fully implemented on
sqlite: DELETE inbox/draft/trash, POST /trash/clear,
PATCH /admin/webhooks/{id}, and POST /daemon/deliver/remote. The reference
docs had presented these as universally available (and wrongly called remote
delivery unimplemented in both backends). Add a memory-backend-gaps note to
http-api, a limitations entry + capability row to storage-backends, and
correct the delivery-model remote-delivery paragraph.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fill in the five explanation stubs against SPEC.md, the package layout, the
auth/refresh-token implementation, and the legacy README:
- mail-v2-overview: what MAIL is, the v1->v2 comms-only refocus, non-goals
- architecture: how protocol/server/client/daemon fit; message lifecycle
- security-model: trust boundaries, bearer + refresh tokens, secret handling,
  production expectations
- mail-v1-legacy: how to read the archived v1 runtime without importing its
  assumptions; how to run legacy tests
- documentation-system: the Divio four-category model and how to place a page

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fill in the final six how-to stubs against the CLIs, scripts, and test config:
- run-server: required env vars, backend choice, checkpoint interval, health check
- run-daemon: daemon credentials, startup/poll behavior, log levels
- run-tests: default run, marker/path subsets, coverage, legacy extra, and how
  to read drift/contract failures and the memory-backend xfail stubs
- send-message-cli: the compose -> send two-step, outbox/inbox inspection,
  validation failures
- manage-user-agents: mail-admin create/list/get/delete for agents/users/daemons
  (password is prompted, not passed as an arg)
- regenerate-api-artifacts: openapi + CLI docs + llms.txt + third-party notices,
  with drift validation

Completes P2; docs/ now has no remaining stubs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
addisonkline and others added 2 commits July 1, 2026 16:16
Replace the transitional 'being reorganized' framing with a proper landing
README: badges (PyPI/Python/license/spec), a highlights section, PyPI + source
install, a runnable local quickstart, a package table, a documentation map
linking into docs/, repository layout, development commands, and
contributing/license sections. Drops the stale note about root scripts
targeting the legacy runtime (scripts/ is v2-only). All relative links verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@rheaton64 rheaton64 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and approved.

Large PR but the shape is honest — 31 commits total accumulated
via rebase, only one source-change line in the docs pass itself.
Verified.

The source change

mail_protocol/core/constants.py: raising the six
*_LEN_MAX constants from 31 → 32 to match SPEC §6. Behavior
change: 32-char identifiers now accepted. Length tests are
constant-relative so they follow automatically.

Chorus impact: none. Chorus doesn't hardcode the 31-limit
anywhere. Verified:

  • chorus/mail.py and chorus/mb.py: no identifier length
    validation on the client side. Chorus accepts whatever
    addresses MAIL accepts.
  • chorus/db/schema.py: mail_credentials.agent_address
    is String(255); swarm is String(128). Both well
    above the 32-char MAIL cap.
  • No hardcoded 31 / 32 constants in chorus's MAIL
    paths.

The docs structure

Divio-style with four clean categories (tutorials, how-tos,
references, explanations). Spot-checked the top-level index
and the layout is clear + navigable. The docs/README.md
"Start Here" section maps intent → category, which is what
Divio-style is supposed to enable.

31 commits post-rebase is a lot but the P0→P1→P2→P3
sequencing shows the work was deliberate — first unblock the
merge conflicts (P0), then fix drift in existing docs (P1),
then fill stubs (P2), then hygiene (P3). Nice discipline.

The accumulated features

Refresh tokens (PR #82), read/unread (PR #84), replies + tags
(PR #74), forwarding (PR #75), draft editing, admin endpoint
address standardization (PR #81), OpenAPI docs (PR #83) —
each was reviewed + approved on its own PR through the dev
list. This PR is the final "wrap the branch" step, not
re-review of any single feature.

Two small observations

Not blocking — just noticing:

  • Repository layout doc would be useful for me as an
    external consumer trying to keep MAIL's structure in my head.
    I see docs/references/repository-layout.md in the file
    list; that solves it.
  • The list-patch NotImplementedError + memory-backend
    stubs
    are correctly documented as such rather than glossed
    over. That's the honest shape — matches what MAIL callers
    will actually see.

Approving

Ship it.

— minichorus-pm

@addisonkline
addisonkline merged commit 1012ea1 into main Jul 1, 2026
2 checks passed
@addisonkline
addisonkline deleted the kline/v2-docs branch July 1, 2026 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants