• v0.35.0 881f0d6717

    joeyr released this 2026-08-23 12:53:40 -05:00 | 99 commits to main since this release

    [0.35.0] — 2026-08-23

    G6: drafts on the Gmail tenant. create_draft and send_draft, behind
    MAIL_SEND_ENABLED plus a writable drafts label. Live pass in
    docs/operations.md — 28 checks, and the probe that preceded it overturned the
    design before a line of it was written.

    Added

    • create_draft and send_draft. The only path on this tenant where a
      message exists in the mailbox before it exists on the wire, which is why they
      were split out of G4 rather than shipped with it.

    • send_draft mints a fresh Message-ID before submitting, ALWAYS, and
      that is the phase rather than a detail of it. probes/gmail_probe_drafts2.py
      submitted a draft's own bytes — what the tool naturally does — and Gmail
      dropped the message entirely: no delivery, no Sent copy, one object left
      in All Mail still wearing \Draft. Google already held that Message-ID, as
      the draft, and deduplicated against it. The tool as designed would then have
      removed the draft and reported the message sent, destroying the only copy of
      something already delivered — 0.28.1's defect reached by a different road. The
      mail tenant mints an id only when the draft carries none; here it is replaced
      whichever it is.

    • The Sent copy is confirmed before the draft is removed. By HEADER Message-ID in the sent label, found on the first look 1.0s after submission
      returned. Every failure answers no, an unreadable sent label included: a
      false negative costs a manual deletion and a false positive costs the message.
      Section 6 of the live pass makes the net catch something — it hides the sent
      label, then asserts the mail went, the draft survived, and the note leads with
      the message having already gone.

    • They register on the write allowlist as well as the send gate. G4's three
      touch no folder in this mailbox and ride MAIL_SEND_ENABLED alone;
      create_draft APPENDs, so on a deployment with nothing writable it could only
      refuse — and an offered tool is one the model promises the user before it
      discovers it cannot deliver. Preflight now asks the finer question the
      registration cannot: is the DRAFTS label specifically readable and writable.

    Fixed

    • BREAKING: get_message on gmail now nests its message under message,
      matching icloudmail and every sibling tool here. It had been flattening the
      message into the envelope since G1, so the two connectors answered different
      shapes for the same tool name — and a model that learned one read None from
      the other, which reads as this message has no subject rather than wrong
      shape
      . Found by G6's live pass, in a G1 tool; the same way G4's pass found a
      G2 defect. tests/test_gmail_drafts.py pins the agreement against both built
      servers, because a test that reads one side cannot see them drift.

    • The boot summary no longer reports folders the tenant does not use.
      sent_folder was printed on Gmail, which appends no Sent copy and never reads
      the setting, and trash_folder and drafts_folder were compared against
      iCloud's defaults on both tenants — so an untouched Gmail deployment reported
      (configured) and told the operator somebody had chosen [Gmail]/Trash.
      Nobody had. One helper now judges each against the defaults for the tenant it
      is running.

    Changed

    • Config.writes_sent_copy asks whether this server has to save the Sent copy
      itself — a question about the provider rather than a tenant name, alongside
      is_mail_tenant and mail_address.

    • send_message's description no longer tells the model this server cannot save
      a draft. Both tools ride the same gate, so the day one registers the other has
      too.

    Downloads