From d05318070e642bcc192f305d23e195f0538ea69f Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 4 Oct 2026 18:52:13 +0700 Subject: [PATCH] feat(queen): HOST launchd cards are tick-only (reported), never dispatched A launchd card is a job on the owner's Mac; launchd owns its schedule and nothing outside the Mac can start it. planCron names DISPATCH_LAUNCHD in the reason; a test holds that a launchd card with a five-field SCHEDULE still gets no trigger and no workflow dispatch. Refs gHashTag/t27#6014 Co-Authored-By: Claude Opus 5.5 --- .../apps/server/src/inngest/plan.ts | 6 ++++-- .../apps/server/tests/api/queen-inngest.test.ts | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/trios/agent-server/apps/server/src/inngest/plan.ts b/trios/agent-server/apps/server/src/inngest/plan.ts index 1a720d7827..ae51e11b70 100644 --- a/trios/agent-server/apps/server/src/inngest/plan.ts +++ b/trios/agent-server/apps/server/src/inngest/plan.ts @@ -14,7 +14,7 @@ import type { SpecCatalog, } from './spec-catalog' -/** `DISPATCH_GITHUB_ACTIONS = "workflow-dispatch"`, everything else `"tick-only"`. */ +/** `DISPATCH_GITHUB_ACTIONS = "workflow-dispatch"`, everything else (launchd included) `"tick-only"`. */ export type Dispatch = 'workflow-dispatch' | 'tick-only' export interface CronPlan { @@ -96,7 +96,9 @@ export function planCron(card: CronCard, s: SchedulerSpec): CronPlan { ? 'HOST inngest: the 999 app owns the schedule (no double fire); DISPATCH_INNGEST = tick-only' : card.host === 'timer' ? 'HOST timer: setInterval inside a 999 process, no remote handle; DISPATCH_TIMER = tick-only' - : `HOST ${card.host}: DISPATCH_RAILWAY_CRON = tick-only` + : card.host === 'launchd' + ? "HOST launchd: a job on the owner's Mac, launchd owns the schedule and nothing outside the Mac can start it; reported, never dispatched; DISPATCH_LAUNCHD = tick-only" + : `HOST ${card.host}: DISPATCH_RAILWAY_CRON = tick-only` } else if (card.schedule === '') { reason = 'SCHEDULE is empty: DISABLED_CARD = tick-only' } else if (!fiveFieldCron(card.schedule)) { diff --git a/trios/agent-server/apps/server/tests/api/queen-inngest.test.ts b/trios/agent-server/apps/server/tests/api/queen-inngest.test.ts index ae6af442bc..7d49249fc9 100644 --- a/trios/agent-server/apps/server/tests/api/queen-inngest.test.ts +++ b/trios/agent-server/apps/server/tests/api/queen-inngest.test.ts @@ -155,6 +155,23 @@ describe('the plan follows the constants', () => { expect(plan.danglingRuns).toEqual([]) }) + it("serves a launchd card (the owner's Mac) tick-only and reported, never dispatched, even with a five-field SCHEDULE", async () => { + const root = specsWith({ + 'crons/zz-mac.t27': cronCard('launchd/trios/doctor', { + HOST: 'pub const HOST : str = "launchd";', + SCHEDULE: 'pub const SCHEDULE : str = "11,41 * * * *";', + RUNS: 'pub const RUNS : [0]str = [];', + }), + }) + const c = await loadSpecCatalog(root) + const p = planCatalog(c).crons.find( + (x) => x.cardId === 'launchd/trios/doctor', + )! + expect(p.dispatch).toBe('tick-only') + expect(p.cron).toBeNull() + expect(p.reason).toContain('DISPATCH_LAUNCHD = tick-only') + }) + it('derives ids and events from the templates and keeps them unique across 59 functions', async () => { const c = await catalogPromise const plan = planCatalog(c)