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

8.1 KiB

Scratchpad — Extension Scheduler Host API

  • Plan slug: extension-scheduler-host-api
  • Created: 2026-01-02

What This Is

Rolling notes for implementing the scheduler host API capability. Captures discoveries, decisions, and implementation details.

Decisions

  • (2026-01-02) Use host API pattern (like cap:http.fetch, cap:storage.kv) rather than manifest-declared schedules — simpler conceptually, maximum flexibility for extension authors.
  • (2026-01-02) Extensions reference endpoints by path (e.g., /sync) not UUID — server resolves path to endpoint_id using the installed version's endpoint table.
  • (2026-01-02) Reuse existing extensionScheduleActions.ts logic — no duplication of validation, quotas, or job runner integration.

Discoveries / Constraints

Capability System

  • (2026-01-02) Capabilities defined in ee/server/src/lib/extensions/providers.ts:
    export const KNOWN_PROVIDER_CAPABILITIES = [
      'cap:context.read',
      'cap:secrets.get',
      'cap:http.fetch',
      'cap:storage.kv',
      'cap:log.emit',
      'cap:ui.proxy',
      'cap:user.read',
    ] as const;
    
  • (2026-01-02) Default capabilities (always granted): cap:context.read, cap:log.emit, cap:user.read
  • (2026-01-02) isKnownCapability() validates capability strings; coerceProviders() normalizes input

Host Bindings Pattern

  • (2026-01-02) SDK interface in sdk/extension-runtime/src/index.ts:
    export interface HostBindings {
      context: { get(): Promise<ContextData> };
      secrets: SecretsHost;
      http: HttpHost;
      storage: StorageHost;
      logging: LoggingHost;
      uiProxy: UiProxyHost;
    }
    
  • (2026-01-02) Extensions receive HostBindings as second argument to handler functions
  • (2026-01-02) Runner implements these interfaces and communicates with host server

Runner Execution Flow

  • (2026-01-02) Runner backend defined in ee/server/src/lib/extensions/runner/backend.ts
  • (2026-01-02) RunnerExecutePayload includes providers?: unknown array — this is how capabilities are communicated
  • (2026-01-02) Runner receives POST /v1/execute with JSON payload including providers list
  • (2026-01-02) Runner is responsible for implementing host bindings based on granted providers

Existing Schedule Actions

  • (2026-01-02) All CRUD in ee/server/src/lib/actions/extensionScheduleActions.ts:
    • listExtensionSchedules(extensionId) — list schedules for an install
    • createExtensionSchedule(extensionId, input) — create with validation
    • updateExtensionSchedule(extensionId, scheduleId, input) — update with reschedule logic
    • deleteExtensionSchedule(extensionId, scheduleId) — delete with job cancellation
    • runExtensionScheduleNow(extensionId, scheduleId) — immediate trigger
  • (2026-01-02) Validation includes: cron format, timezone, endpoint ownership, quotas (50 max, 5-min minimum)
  • (2026-01-02) Uses ensureExtensionPermission() for auth — we need equivalent for host API context

Key Implementation Gap

  • (2026-01-02) Current actions use getCurrentUser() and hasPermission() — host API calls come from Runner, not user session
  • (2026-01-02) Need to create internal versions that accept install context directly (tenant_id, install_id) rather than deriving from user session
  • (2026-01-02) Could wrap existing actions or create parallel internal functions

Commands / Runbooks

  • Validate plan: python3 ~/.codex/skills/alga-plan/scripts/validate_plan.py ee/docs/plans/2026-01-02-extension-scheduler-host-api
  • Capability definitions: ee/server/src/lib/extensions/providers.ts
  • SDK host bindings: sdk/extension-runtime/src/index.ts
  • Runner backend: ee/server/src/lib/extensions/runner/backend.ts
  • Schedule actions: ee/server/src/lib/actions/extensionScheduleActions.ts
  • Sample extension using host APIs: sdk/samples/component/service-proxy-demo/src/handler.ts
  • Parent plan (scheduled tasks): ee/docs/plans/2026-01-01-extension-scheduled-tasks/

Implementation Notes

Endpoint Resolution

Extensions will reference endpoints by path:

await host.scheduler.create({ endpoint: '/sync', cron: '0 * * * *' });

Server-side resolution:

SELECT id FROM extension_api_endpoint
WHERE version_id = $versionId
  AND path = $path
  AND method IN ('GET', 'POST');

Internal API vs Action Reuse

Two approaches for server-side handler:

Option A: Internal HTTP API

  • Create /api/internal/extensions/scheduler/* routes
  • Runner calls these with auth token identifying install
  • Routes delegate to modified action functions

Option B: Direct action adaptation

  • Create *ForInstall variants of existing actions
  • Accept (tenantId, installId, ...) instead of using getCurrentUser()
  • Runner calls these via RPC mechanism

Recommendation: Start with Option A for clearer separation; consider B if performance is an issue.

Context Passing to Runner

The Runner receives execution context:

{
  context: {
    tenant_id: string,
    registry_id: string,
    install_id: string,
    version_id: string,
    content_hash: string,
    // ...
  },
  providers: ['cap:scheduler.manage', ...],
  // ...
}

Runner can use install_id and tenant_id from context to scope all scheduler operations.

Open Questions (Resolved)

  • How does Runner currently call back to host for cap:storage.kv?

    • Answer: HTTP callback to internal API endpoint /api/internal/ext-storage/install/[installId]
    • Runner uses STORAGE_API_BASE_URL and RUNNER_STORAGE_API_TOKEN env vars
    • Auth via x-runner-auth header
  • Should we expose runNow() via the host API, or is that admin-only?

    • Answer: Admin-only. Extensions should not be able to bypass scheduling.
  • Do we need a getEndpoints() API so extensions can discover their own schedulable endpoints?

    • Answer: Yes! Added getEndpoints() to return all endpoints with schedulable flag.

Implementation Summary (2026-01-02)

Files Created/Modified

Server-side:

  • ee/server/src/lib/extensions/providers.ts — Added cap:scheduler.manage to KNOWN_PROVIDER_CAPABILITIES
  • ee/server/src/lib/extensions/schedulerHostApi.ts — New internal API for schedule operations
  • ee/server/src/app/api/internal/ext-scheduler/install/[installId]/route.ts — Internal REST endpoint for Runner callbacks

SDK:

  • sdk/extension-runtime/src/index.ts — Added SchedulerHost interface and all related types

Runner (Rust):

  • ee/runner/wit/extension-runner.wit — Added scheduler types and interface
  • ee/runner/src/providers/mod.rs — Added CAP_SCHEDULER_MANAGE constant
  • ee/runner/src/engine/host_api.rs — Full scheduler host implementation with HTTP callbacks

Sample Extension:

  • sdk/samples/component/scheduler-demo/ — Full sample extension demonstrating:
    • Self-configuration on /api/setup endpoint
    • Listing schedules
    • Deleting schedules
    • Schedulable endpoints (/api/status, /api/heartbeat)

Key Patterns Used

  1. HTTP Callback Pattern: Runner uses HTTP POST to internal API (same as cap:storage.kv)
  2. Install Context: Derived from install config lookup using installId in URL path
  3. Endpoint Resolution: Extensions specify "METHOD /path", server resolves to endpoint_id
  4. Error Translation: HTTP status codes mapped to WIT error enums

Completed (2026-01-02)

All 84 features are now implemented:

  • F067: Expose metrics for scheduler API calls — Added OpenTelemetry metrics (counter, histogram, errors) to schedulerHostApi.ts
  • F071: Document scheduler host API in SDK documentation — Created comprehensive guide at sdk/docs/guides/scheduler-host-api.md
  • F080: Rate limiting on create/update operations via host API — Added in-memory sliding window rate limiter (10 ops/min) to internal API route

Testing Notes

The Runner Rust code compiles successfully (cargo check passes). Sample extension handler tests defined but require running in WASM context. Full integration testing requires:

  1. Install sample extension with cap:scheduler.manage
  2. Call /api/setup endpoint
  3. Verify schedules created in database and job runner