SDK v2 Phase 5 (3/3): scheduled triggers with the app principal
Radin committed
- Author
- Radin · radin@coline.appAug 10, 2026 at 19:47
- Committer
- Radin · radin@coline.appAug 10, 2026 at 19:47
- Parents
- b354dc80b05c
SDK v2 Phase 5 (3/3): scheduled triggers with the app principal
Apps run on a clock without the developer running a scheduler (plan
§11.3):
SDK:
- trigger.schedule protocol kind; handlers.onSchedule in defineApp
(runtime completeness enforced: Coline-hosted schedules require the
handler); createColineAppHandler routes the kind for external
backends; ColineScheduleContextV2 carries scheduleId + firedAt.
Platform:
- lib/apps/cron.ts: 5-field cron matcher (*, lists, ranges, steps,
standard either-day semantics) — 7 unit tests.
- lib/apps/schedules.ts: the minute drain walks active v2 installs,
fires matching schedules, and claims each (install, schedule, minute)
atomically in app_schedule_state via a conditional UPDATE + INSERT
race — overlapping drains never double-fire.
- POST /api/internal/apps/schedules (internal-secret guarded, mirrors
the calendar reminders drain): the external minute scheduler adds one
more target.
The app principal (plan §12.2's trigger-context rule, now real):
- Invocation claims accept userId: null; dispatch emits actor
{kind:'app'}; the guard already skips membership for app principals.
- App-scoped runs keep full access to install-scoped capabilities
(storage, indexing, notifications, net.fetch) while user-ACL file
operations fail closed with requires_user_principal — a scheduled
job cannot impersonate anyone's file access.
- Record envelopes record createdBy: 'app' for scheduled writes.
Realtime bootstrap dispatches trigger.schedule to runtime.onSchedule.
Verified: cron 7 tests, sdk 81, storage verify 19 checks green; web +
realtime typecheck clean; eslint clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>5 files changed+106−1