Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

10 KiB

PRD — Extension Scheduler Host API

  • Slug: extension-scheduler-host-api
  • Date: 2026-01-02
  • Status: Draft

Summary

Expose the existing extension scheduled tasks system to extensions via a new cap:scheduler.manage capability and corresponding host API. This allows extension authors to programmatically create, update, delete, and list their own scheduled tasks from within extension code, enabling extensions to set up required schedules on install without manual admin configuration.

Problem

Currently, extension scheduled tasks can only be created via the admin UI or server actions (extensionScheduleActions.ts). Extension authors have no way to automatically configure schedules when their extension is installed. This creates friction:

  • Admins must manually set up schedules after installing an extension
  • Extensions cannot adapt their scheduling to runtime configuration
  • No self-service for extension developers who need periodic tasks

Extensions often need predictable periodic execution (sync jobs, cache refresh, reconciliation) and should be able to configure this themselves.

Goals

  • Expose schedule CRUD operations to extensions via a new host API capability
  • Allow extensions to manage only their own schedules (scoped to their install)
  • Reuse existing validation, quotas, and guardrails from extensionScheduleActions.ts
  • Follow the established host API pattern (cap:http.fetch, cap:storage.kv, etc.)
  • Enable extension authors to set up schedules during any handler execution (e.g., a /setup endpoint)

Non-goals

  • Modifying the underlying schedule execution infrastructure
  • Allowing extensions to manage schedules for other extensions
  • Changing the admin UI for schedule management
  • Adding new schedule features (those belong to the parent scheduled tasks plan)
  • Lifecycle hooks (onInstall, onUpdate) — extensions call the API from their own endpoints

Users and Primary Flows

Primary persona: Extension developer building an Alga PSA extension

Flow: Set up schedules on extension install

  1. Extension manifest declares cap:scheduler.manage capability
  2. Extension exposes a /setup or /init endpoint
  3. After install, admin (or extension UI) calls the /setup endpoint
  4. Extension handler calls host.scheduler.list() to check existing schedules
  5. If schedules don't exist, extension calls host.scheduler.create(...) for each required schedule
  6. Extension returns success; schedules are now active

Flow: Modify schedule based on user configuration

  1. User changes extension config (e.g., sync interval from 1 hour to 6 hours)
  2. Extension's config-save handler retrieves the new interval
  3. Handler calls host.scheduler.update(scheduleId, { cron: newCron })
  4. Schedule is updated; next execution uses new timing

Flow: Clean up schedules on extension disable/uninstall

  1. Extension provides a /cleanup endpoint (optional)
  2. Handler calls host.scheduler.list() to get all schedules
  3. Handler calls host.scheduler.delete(id) for each schedule
  4. (Note: system also auto-cleans schedules on uninstall via existing cleanup logic)

UX / UI Notes

No UI changes required. This is a backend API exposed to extension WASM code.

Extension developers interact via the HostBindings interface:

export async function handler(request: ExecuteRequest, host: HostBindings) {
  // List existing schedules for this extension install
  const schedules = await host.scheduler.list();

  // Create a new schedule
  const result = await host.scheduler.create({
    endpoint: '/sync',      // path from extension's manifest
    cron: '0 * * * *',      // every hour
    timezone: 'UTC',
    name: 'Hourly Sync',
    payload: { full: false }
  });

  // Update a schedule
  await host.scheduler.update(scheduleId, { enabled: false });

  // Delete a schedule
  await host.scheduler.delete(scheduleId);
}

Requirements

Functional Requirements

Capability registration

  • Add cap:scheduler.manage to KNOWN_PROVIDER_CAPABILITIES in providers.ts
  • Extensions must declare this capability in their manifest to access scheduler APIs
  • Capability is not granted by default (must be explicitly requested and granted)

Host API interface (extension-runtime SDK)

  • Add SchedulerHost interface to HostBindings in sdk/extension-runtime/src/index.ts
  • Define request/response types for each operation
interface SchedulerHost {
  list(): Promise<ScheduleInfo[]>;
  get(scheduleId: string): Promise<ScheduleInfo | null>;
  create(input: CreateScheduleInput): Promise<CreateScheduleResult>;
  update(scheduleId: string, input: UpdateScheduleInput): Promise<UpdateScheduleResult>;
  delete(scheduleId: string): Promise<DeleteScheduleResult>;
}

interface ScheduleInfo {
  id: string;
  endpointPath: string;
  endpointMethod: string;
  name?: string;
  cron: string;
  timezone: string;
  enabled: boolean;
  payload?: unknown;
  lastRunAt?: string;
  lastRunStatus?: string;
  lastError?: string;
}

interface CreateScheduleInput {
  endpoint: string;      // path like "/sync" - resolved to endpoint_id
  cron: string;
  timezone?: string;
  enabled?: boolean;
  name?: string;
  payload?: unknown;
}

interface CreateScheduleResult {
  success: boolean;
  scheduleId?: string;
  error?: string;
  fieldErrors?: Record<string, string>;
}

// UpdateScheduleInput and DeleteScheduleResult similar patterns

Runner implementation

  • Runner receives cap:scheduler.manage in providers array
  • Runner implements host bindings that call back to the Alga server
  • Server exposes internal API endpoints for Runner to call (or Runner proxies to existing actions)

Server-side handler

  • Create internal API routes or RPC handler for scheduler operations
  • Route handler validates the calling extension/install context
  • Delegate to existing extensionScheduleActions functions (reuse validation, quotas, job runner integration)
  • Scope all operations to the calling extension's install_id

Endpoint resolution

  • Extensions reference endpoints by path (e.g., /sync) not by UUID
  • Server resolves path to endpoint_id using the installed version's endpoint table
  • Reject paths that don't exist in the extension's manifest

Non-functional Requirements

Security

  • Extensions can only manage schedules for their own install (enforced by server)
  • Existing quotas apply: max 50 schedules per install, min 5-min interval
  • Rate limiting on create/update operations (prevent abuse)
  • No cross-tenant or cross-extension access

Reliability

  • Reuse existing transactional guarantees from extensionScheduleActions
  • Errors are returned as structured results, not thrown exceptions (WASM boundary)

Observability

  • Log scheduler host API calls with extension/install context
  • Include in extension execution metrics

Data / API / Integrations

New capability

// ee/server/src/lib/extensions/providers.ts
export const KNOWN_PROVIDER_CAPABILITIES = [
  // ... existing
  'cap:scheduler.manage',
] as const;

SDK interface addition

// sdk/extension-runtime/src/index.ts
export interface SchedulerHost {
  list(): Promise<ScheduleInfo[]>;
  get(scheduleId: string): Promise<ScheduleInfo | null>;
  create(input: CreateScheduleInput): Promise<CreateScheduleResult>;
  update(scheduleId: string, input: UpdateScheduleInput): Promise<UpdateScheduleResult>;
  delete(scheduleId: string): Promise<DeleteScheduleResult>;
}

export interface HostBindings {
  // ... existing bindings
  scheduler: SchedulerHost;
}

Runner-to-server communication

  • Option A: Runner calls back to Alga server via HTTP (internal API)
  • Option B: Runner proxies through existing execution response mechanism
  • Decision: TBD based on Runner architecture exploration

Internal API (if using HTTP callback)

POST /api/internal/extensions/scheduler/list
POST /api/internal/extensions/scheduler/get
POST /api/internal/extensions/scheduler/create
POST /api/internal/extensions/scheduler/update
POST /api/internal/extensions/scheduler/delete

All endpoints receive install context (tenant_id, install_id) from Runner authentication.

Security / Permissions

  • Extensions must declare cap:scheduler.manage in manifest
  • Tenant admin must grant the capability during install (normal capability grant flow)
  • All operations are scoped to the calling extension's install_id
  • Existing validation applies (cron format, timezone, endpoint existence, quotas)
  • No elevation of privilege: extensions cannot bypass quotas or create schedules for other extensions

Observability

  • Log each host API call: operation, install_id, success/failure, duration
  • Include trigger=host_api in schedule creation metadata (vs trigger=admin_ui)
  • Expose metrics: extension_scheduler_api_calls_total{operation, status}

Rollout / Migration

  1. Add capability to KNOWN_PROVIDER_CAPABILITIES
  2. Add SchedulerHost interface to SDK
  3. Implement Runner host bindings
  4. Add server-side handler (reusing extensionScheduleActions)
  5. Update SDK documentation with usage examples
  6. No migration needed: this is additive functionality

Open Questions

  1. Runner callback mechanism: How does the Runner currently call back to the host for other capabilities (http.fetch, storage.kv)? We should use the same pattern.
  2. Endpoint reference format: Should extensions reference endpoints by path only, or by method + path? (Recommendation: path only, since schedules are limited to GET/POST anyway)
  3. Error format: Should we expose field-level errors to extensions, or simplify to just error: string?

Acceptance Criteria (Definition of Done)

  • cap:scheduler.manage is a recognized capability in the system
  • Extensions with the capability can call host.scheduler.list() and receive their schedules
  • Extensions can create schedules by endpoint path; server resolves to endpoint_id
  • Extensions can update and delete their own schedules
  • Existing quotas and validation are enforced
  • Extensions cannot access schedules from other extensions or tenants
  • SDK types are published and documented
  • At least one sample extension demonstrates schedule self-configuration