Repository navigation
feat: mailbox source, REPORT, add to collection (#2246, #2247, #2260) - #2262
Merged
Merged
Conversation
…, #2260) Adds REPORT to api_invoke_endpoint, the caldav kind (calendar_list, calendar_events, calendar_event), the imap kind (mail_search, mail_message, mail_thread, mail_attachment), a script setting mail_actions (migration 000218, PUT /api/v1/portal/scripts/{id}/mail-actions), and add to collection from the asset viewer (POST /api/v1/portal/collections/{id}/items, GET /api/v1/portal/assets/{id}/collection-targets, asset_id on POST /api/v1/portal/collections). The caldav kind reads a calendar account over CalDAV with PROPFIND, REPORT and GET only. Recurring events are expanded in the event's own zone, applying EXDATE and RECURRENCE-ID overrides, and non-IANA zone names are mapped. Author-written fields are returned under external, cleaned of invisible characters (internal/untrusted). The imap kind reads the folders a connection names, over IMAPS or STARTTLS, with EXAMINE and BODY.PEEK. A message is shown only when its From is on allow_senders and, with require_auth_pass, the receiving server's Authentication-Results records an aligned SPF, DKIM or DMARC pass; other messages are counted and never returned (internal/mailtrust). HTML is reduced to visible text and quoted history is collapsed (internal/imapmail). mail_attachment lands the file in the caller's resource library. With mail_actions off, a run that has called a mail_* tool is refused every later write-class tool call and every output (internal/mailbarrier, wrapping the run's caller and exporter in platform runs, drafts and endpoint requests); one run makes at most 500 mail calls. The dev stack runs Radicale and Dovecot for the acceptance criteria, and dev-smoke checks both. One bug fixed: 1. cfgmap.Int64 read an integer written as a string ("9993", the form a YAML value filled from the environment arrives in) as the default instead of the value. Closes #2246 Closes #2247 Closes #2260
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2262 +/- ##
==========================================
- Coverage 94.78% 94.78% -0.01%
==========================================
Files 1406 1420 +14
Lines 113978 115028 +1050
==========================================
+ Hits 108036 109026 +990
- Misses 5924 5983 +59
- Partials 18 19 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…2247) Replaces the caldav and imap connection kinds, their tools (calendar_list, calendar_events, calendar_event, mail_search, mail_message, mail_thread, mail_attachment) and the mail_actions script setting with an imap input on webhook sources: config.input imap, config.imap (host, port, tls, tls_ca_bundle_pem, folders, allow_senders, require_auth_pass, trusted_authserv_id, poll_seconds, max_message_bytes, max_attachment_bytes) and auth mode imap with the mailbox password as the write-only secret. Migration 000218 is now webhook_imap_marks; GET /api/v1/admin/webhooks/sources/{name} carries each folder's marks under imap. One replica at a time (advisory lock) checks each enabled imap source on its interval (internal/webhook/whimap): each folder is opened with EXAMINE and fetched with BODY.PEEK, read above its UID watermark, at most 200 messages per check. A message from an allowed sender with an aligned SPF, DKIM or DMARC pass recorded by the trusted receiving server is written as one event to the raw segments the HTTP receiver writes (event_id the Message-ID, event_type mail, key the folder, sender-written text under external), so the table, compaction, retention and alerts are the same as for a posted source. Other mail is counted on its folder and not stored. Attachments are managed resources under webhooks/{source}/attachments/ in the source's library, named on the event, and are deleted with the source. Saving an imap source, on create and on update, signs in and opens each folder and is refused when it cannot. A source's input is kept on update. The receiver skips imap sources. Admin > Webhooks gains a mailbox choice in the editor and a Mailbox section on the source's page with each folder's stored and withheld counts and last error. docs/server/webhooks.md has "A mailbox as a source"; the caldav and imap pages are removed. REPORT on api_invoke_endpoint stays (#2246). The dev stack keeps Radicale for its acceptance criterion and Dovecot for #2247's, and dev-smoke checks both. Unused parts of internal/imapmail and internal/mailtrust that only the tools called are removed. Closes #2246 Closes #2247
TestPoll_FilesTrustedMailAsEvents searched the JSON of the stored events for "999", a number from a withheld message. That JSON carries each payload as base64 beside content hashes and timestamps, so the search could match by chance (it did in CI) and could never find a withheld sentence. It now searches each event's id and decoded payload for the full withheld sentences, and the unused mustJSON helper is removed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an
imapinput to webhook sources (config.input,config.imap, auth modeimap, migration000218_webhook_imap_marks,imaponGET /api/v1/admin/webhooks/sources/{name}),REPORTonapi_invoke_endpoint, and Add to collection in the asset viewer (POST /api/v1/portal/collections/{id}/items,GET /api/v1/portal/assets/{id}/collection-targets,asset_idonPOST /api/v1/portal/collections). No MCP tool is added. One bug fixed, ininternal/cfgmap.Closes #2246
Closes #2247
Closes #2260
A mailbox as a webhook source (#2247)
A webhook source with
config.input: imapis not posted to: the platform checks a mailbox on an interval and files each new message from an allowed, authenticated sender as one event in the raw segments the HTTP receiver writes. The source's table, compaction to Parquet, retention and alerts are the same as for a posted source. The mail reaches a script or an agent only through the table and the resources.auth.mode: imap,auth.username,auth.secretconfig.imap.host,port,tlsconfig.imap.tls_ca_bundle_pemconfig.imap.foldersconfig.imap.allow_senders@domainentriesconfig.imap.require_auth_pass,trusted_authserv_idAuthentication-Resultscarrying the trusted idconfig.imap.poll_secondsconfig.imap.max_message_bytes,max_attachment_bytesHow it reads (
internal/webhook/whimap):bglooploopwebhook_imap_poll, every 15 seconds for sources whose interval has passed).BODY.PEEK; nothing is marked seen, moved, deleted or sent.webhook_imap_marks, with the stored and withheld counts and the last error. A renumbered folder is read again; the event id is the Message-ID, so compaction keeps one copy. At most 200 messages per folder per check, oldest first, and a failed check leaves the watermark where it was.internal/mailtrust).event_typemail,keythe folder, and a payload with what the platform read (folder, uid, dates, from address, authentication result, size, attachments) and, underexternal, what the sender wrote, cleaned of invisible, bidirectional and control characters (internal/untrusted), with HTML reduced to visible text and hidden elements dropped (internal/imapmail), beside anotice.webhooks/{source}/attachments/{date}/{folder}-{uidvalidity}-{uid}/in the source's library, named on the event byuriandresource_id; reading a message again writes a new version. A source's attachments are deleted with the source (windowResources.DeleteAttachments).Saving a source, on create and on update, signs in and opens each folder and is refused with the server's answer when it cannot (
whadmin). A source's input is kept on update. The HTTP receiver skips imap sources, so/hooks/{name}answers 404 for one.Admin > Webhooks: the source editor offers "A mailbox the platform checks (IMAP)" with Mail server and What is read groups in place of the authentication and events groups; the source's page shows a Mailbox section (server, login, allowed senders, and per folder the messages stored, withheld because the sender is not allowed or not authenticated, last check and last error) in place of the sender address, requests and rejections.
REPORT on api connections (#2246)
api_invoke_endpointacceptsREPORTbesidePROPFIND: forwarded unchanged with its XML body andDepthheader, honored on a catalog'sx-webdav-methodcarrier operation, and classified as a read byinternal/toolwrite.decode: "xml"parses the multistatus. This is what a CalDAVcalendar-queryorcalendar-multigetis sent with. Calendars as a polled source, like mail, is #2263.Add to collection (#2260)
Add to collection in the asset viewer toolbar opens a picker of the collections the reader can edit (their own, those shared as Editor, every collection for an administrator), most recently updated first, with search. A collection already holding the asset says in which sections; one whose every section holds it cannot be chosen. A collection with several sections asks for the section, the last unused one preselected; New collection creates one with the asset as its first item. The confirmation names the collection and section and opens it.
POST /api/v1/portal/collections/{id}/itemsappends one item at the end of the named section, the last section, or a new untitled section in a collection with none, holding the collection row for the transaction. 409 for an asset already in the section, a full section, or a section no longer in the collection; 403 for a caller who cannot edit the collection or open the asset. The SQL is ininternal/portal/collitems.GET /api/v1/portal/assets/{id}/collection-targetslists the editable collections with their sections andhas_asset.POST /api/v1/portal/collectionstakesasset_id; a failed seed removes the new collection.Bug fixed
cfgmap.Int64read an integer written as a string ("9993", the form a YAML value filled from the environment arrives in) as the default rather than the value, for every connection kind. It now parses a decimal string; a non-numeric string still falls back.Dev stack
dovecot(Dovecot 2.4.1 over IMAPS,dev/dovecot, port 9993, certificate generated intodev/.tls/dovecot) andradicale(Radicale 3.5.4.0,dev/radicale, port 5232) run beside the other fixtures. The Dovecot overlay setsdefault_vsz_limit = unlimited, without which its services fail to start under x86 emulation on an arm machine.dev-smokechecks both through the platform: an imap source saved against Dovecot, and a PROPFIND to Radicale through an api connection.Readers of this state
whsource.Config.input,whsource.Config.imap,whsource.IMAP:WebhookConfigandWebhookIMAPinui/src/api/admin/types/webhooks.ts, read bywebhookForm.tsandimapForm.ts(the editor) andMailboxSection.tsx; MSWui/src/mocks/handlers/webhooks.tsandui/src/mocks/data/webhooks.ts.webhookapi.SourceDetail.imap,whimap.Mark:WebhookSourceDetail.imapandWebhookIMAPFolder, read byWebhookDetailPage.tsxandMailboxSection.tsx; the MSW detail handler returns them for a mailbox source. A source'spathis empty for one;senderURLreturns empty for it and the sender section is not shown.AppendedItem,CollectionTarget,SectionSummary,appendItemRequest,collectionTargetsResponse,createCollectionRequest.asset_id:ui/src/api/portal/types.tsandui/src/api/portal/hooks/collections.ts, read byAddToCollectionDialog.tsx; MSWui/src/mocks/handlers.ts.useCreateCollection's other caller (the collections page) does not sendasset_id.POST /portal/collectionsdescription change: no reader depends on the text.Tests
build/<n>/acceptance.jsonl):issue_2247_test.go, 6 criteria against Dovecot: an allowed message is one row with its hidden HTML gone and its attachment a readable resource; a stranger's and unauthenticated mail are counted and not stored; the messages stay unseen and in place; a wrong password is refused at save; nomail_*orcalendar_*tool is registered; deleting the source removes its attachments.issue_2246_test.go, 1 criterion against Radicale:REPORTwith acalendar-queryfor one day answers 207 with only that day's events.issue_2260_test.go, 3 criteria.internal/webhook/whimapagainst go-imap's in-memory server over TLS,whsource,whadmin(the mailbox check on create and update, the input kept, attachments deleted),webhookapi,webhookwire(PutFile,DeleteAttachments),internal/portal/collitemswith sqlmock and a real-Postgres append test, and vitest for the mailbox form, editor and detail page and the collection picker.make preverify(migration check on pg16 and pg17, dev-smoke) andmake verifypassed.Docs
docs/server/webhooks.mdgains "A mailbox as a source";api-catalogs.mdandportal/assets.mdare updated;CLAUDE.md,llms.txtandllms-full.txtfollow, anddev/README.mdlists the two servers.