Skip to content

fix(hub): cron ticker delivers as an authorized mailbox sender (CL-8533) - #919

Merged
TheGreatAxios merged 2 commits into
mainfrom
cl-8533-cron-sender
Sep 18, 2026
Merged

TheGreatAxios merged 2 commits into
mainfrom
cl-8533-cron-sender

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

A cron schedule fires with nobody signed in, so it could not ride the mailbox persist path: that path authorizes its sender against a live routable endpoint, and cron@<domain> is not one — every tick failed.

A due schedule is a system trigger, exactly like an inbound webhook, so it now takes the same route an inbound webhook takes: materialize the run's mail-triggered grants, hand them to the sidecar, then route a signed trigger frame in which the run is the authenticated sender of its own trigger mail. No authorization check is skipped — the frame never enters the mailbox sender path at all.

That frame assembly already existed inside @corbits/webhooks, so it lives there rather than in a new hub file (the hub is upstream server.ts plus one Corbits block):

  • @corbits/webhooks now exports createRunTriggerDeliverer with a caller-supplied sender local part and subject (feat: export createRunTriggerDeliverer for other system triggers webhooks#4, no behavior change on the webhook path); repinned here.
  • packages/cron gains createRunTriggerCronDeliver, which fans a due schedule's recipients out over that deliverer.
  • DeliverCronMail names the tenant instead of inventing a from: only the host knows which addresses its transport authorizes.
  • A tick no longer stops at a failed delivery — each schedule's failure is reported through onDeliveryError and every due row still advances, so one undeliverable schedule cannot re-fire every minute forever.
  • The webhook and cron mounts share one systemTriggerMailRouter.

Files: apps/hub/package.json, apps/hub/src/server.ts, packages/cron/src/{index,ticker,deliver,deliver.test}.ts.

A due cron schedule was handed to the mailbox persist path with a
synthesized `cron@<domain>` sender. That path authorizes a sender against
a live routable endpoint — only an open workflow run address resolves —
so the mailbox write was refused and the hub's own persist threw for the
same reason. Nothing was delivered, and the run was never triggered:
persisting mail records does not fire a run.

A schedule firing with nobody signed in is a trigger, not a person's mail.
It now takes the route an inbound webhook already takes: materialize the
run's mail-triggered grants, hand them to the sidecar, and route a signed
frame in which the run is the authenticated sender of its own trigger
mail. No authorization check is skipped; the frame never enters the
mailbox sender path.

The ticker no longer invents a sender address — it names the tenant and
leaves the identity to the host — and one schedule's failed delivery is
now reported and isolated instead of aborting the whole tick.
@linear-code

linear-code Bot commented Sep 18, 2026

Copy link
Copy Markdown

CL-8533

The hub is upstream server.ts plus one Corbits block, so the run-trigger
frame assembly does not belong in a hub file. @corbits/webhooks now
exports the deliverer it already had internally, with a caller-supplied
sender local part; @corbits/cron fans a due schedule's recipients out
over it.
@TheGreatAxios
TheGreatAxios merged commit 58805fd into main Sep 18, 2026
4 checks passed
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.

1 participant