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

17 KiB

SCRATCHPAD: Workflow Scheduling Actions

2026-04-25 — Plan Created

Worktree / Branch

  • Worktree: /Users/roberisaacs/alga-psa.worktrees/feature/workflows-scheduling-actions
  • Current git status before plan edits: package-lock.json already modified. This plan did not intentionally touch it.
  • Plan folder: ee/docs/plans/2026-04-25-workflow-scheduling-actions/

User Request Summary

Add richer workflow scheduling actions so workflow authors can express dispatcher language directly:

  • scheduling.reschedule
  • scheduling.reassign
  • scheduling.cancel
  • scheduling.complete

Also keep/add the read side:

  • scheduling.find_entry
  • scheduling.search_entries

Target lifecycle event alignment:

  • APPOINTMENT_CREATED
  • APPOINTMENT_RESCHEDULED
  • APPOINTMENT_ASSIGNED
  • APPOINTMENT_CANCELED
  • APPOINTMENT_COMPLETED
  • APPOINTMENT_NO_SHOW

This pass plans the four requested lifecycle write actions plus read actions. NO_SHOW is not included unless scope changes.

Source Investigation Notes

Workflow action architecture

  • Action file to change: shared/workflow/runtime/actions/businessOperations/scheduling.ts
  • Registration is already wired through registerSchedulingActions().
  • Business operation registration flows through shared/workflow/runtime/actions/registerBusinessOperationsActions.ts and shared/workflow/runtime/init.ts.
  • Designer grouping is prefix-based. scheduling.* maps to the built-in Scheduling group in shared/workflow/runtime/designer/actionCatalog.ts.

Current scheduling workflow action state

  • shared/workflow/runtime/actions/businessOperations/scheduling.ts currently only registers scheduling.assign_user.
  • scheduling.assign_user:
    • Validates user exists.
    • Requires the assigned user to have a role named Technician.
    • Validates linked ticket/project task exists.
    • Handles conflict modes fail, shift, override.
    • Inserts schedule_entries and schedule_entry_assignees directly.
    • Inserts unresolved schedule_conflicts rows on override.
    • Writes workflow run audit via writeRunAudit().
    • Does not currently publish APPOINTMENT_CREATED or APPOINTMENT_ASSIGNED workflow events.

Shared workflow helper state

  • shared/workflow/runtime/actions/businessOperations/shared.ts contains useful helpers:
    • withTenantTransaction()
    • requirePermission()
    • writeRunAudit()
    • throwActionError()
    • uuidSchema
    • isoDateTimeSchema
  • withTenantTransaction() resolves the workflow actor user from workflow run/definition metadata and sets tenant RLS with set_config('app.current_tenant', ...).

Existing scheduling model/actions

  • packages/scheduling/src/models/scheduleEntry.ts is the tenant-explicit model for schedule entries.
    • Supports create(), update(), delete(), get(), getAll(), recurrence handling, and assignee helper methods.
    • update() supports recurrence scopes using IEditScope: single, future, all.
    • Virtual recurring ids use an underscore pattern: <masterEntryId>_<timestamp>.
    • File currently has // @ts-nocheck; implementation should be covered by DB-backed tests if imported/used.
  • packages/scheduling/src/actions/scheduleActions.ts is the UI/server action layer.
    • Uses withAuth, hasPermission, createTenantKnex, and withTransaction.
    • Publishes legacy event-bus events: SCHEDULE_ENTRY_CREATED, SCHEDULE_ENTRY_UPDATED, SCHEDULE_ENTRY_DELETED.
    • Publishes workflow domain events using publishWorkflowEvent:
      • APPOINTMENT_CREATED
      • APPOINTMENT_RESCHEDULED
      • APPOINTMENT_ASSIGNED
      • APPOINTMENT_CANCELED
      • APPOINTMENT_COMPLETED
      • APPOINTMENT_NO_SHOW
      • schedule block and technician dispatch events.
    • Because of withAuth, these server actions are not a clean direct dependency for workflow runtime action handlers.

Existing event builders/schemas

  • Appointment event builders: shared/workflow/streams/domainEventBuilders/appointmentEventBuilders.ts
    • shouldEmitAppointmentEvents(entry) returns true for work_item_type === 'appointment_request' || 'ticket'.
    • getTicketIdFromScheduleEntry(entry) maps ticket-linked entries.
    • Status helpers accept tolerant canceled/completed/no-show spellings.
    • Builders include buildAppointmentRescheduledPayload, buildAppointmentAssignedPayload, buildAppointmentCanceledPayload, buildAppointmentCompletedPayload.
  • Workflow runtime event payload schemas: shared/workflow/runtime/schemas/schedulingEventSchemas.ts
  • Event package schemas: packages/event-schemas/src/schemas/domain/schedulingEventSchemas.ts
  • Existing APPOINTMENT_ASSIGNED schema is single-assignee shaped: previousAssigneeId?, previousAssigneeType?, newAssigneeId, newAssigneeType.

Schedule persistence tables

  • Initial schedule_entries table in server/migrations/202409071803_initial_schema.cjs.
  • schedule_entry_assignees created in server/migrations/20241227233407_create_schedule_entry_assignees.cjs.
  • user_id removed from schedule_entries in server/migrations/20241228003050_remove_user_id_from_schedule_entries.cjs.
  • Recurrence columns added in server/migrations/20241227234500_add_original_entry_id_to_schedule_entries.cjs:
    • original_entry_id
    • is_recurring
  • Ad hoc schedule entries made possible by server/migrations/20250103172553_add_adhoc_schedule_entries.cjs.
  • is_private added in server/migrations/20250515104157_add_private_flag_to_schedule_entries.cjs.
  • interaction work item type added in server/migrations/20250602153500_add_interaction_work_item_type.cjs.
  • appointment_request work item type added in server/migrations/20251111190000_add_appointment_request_work_item_type.cjs.

Permissions

  • Current scheduling.assign_user requires user_schedule:create.
  • Existing role/permission migrations define user_schedule:create, read, update, delete.
  • UI/server scheduling actions generally use:
    • user_schedule:read for reads.
    • user_schedule:update for broad edits/assignments.
    • Delete action has custom validation but should map to product decision for workflow cancel semantics.

Approach Options Considered

  1. Extend scheduling.ts with local helpers — recommended for this pass.
    • Smallest change in workflow action layer.
    • Avoids withAuth server action coupling.
    • Some duplication with Scheduling server action event logic.
  2. Extract scheduling domain service — best long-term architecture.
    • More reusable.
    • Larger refactor and not needed to create first safe workflow action surface.
  3. Call existing Scheduling server actions — not recommended.
    • They are session/auth-bound and not designed for workflow runtime's explicit tenant/run actor context.

Decisions Made in Draft PRD

  • Plan includes both read side and write side actions.
  • Do not include scheduling.no_show unless explicitly added to scope.
  • Prefer no DB migration for v1; store reason/note/outcome in notes and/or audit details.
  • Use existing designer grouping by scheduling.* prefix.
  • Treat recurring scopes as single | future | all following IEditScope.

Decisions Confirmed Before Implementation

Confirmed by user on 2026-04-25:

  1. scheduling.reassign v1 supports multiple technicians and emits one APPOINTMENT_ASSIGNED per newly assigned user.
  2. scheduling.cancel requires user_schedule:update because it marks status canceled rather than deleting rows.
  3. Workflow action event publishing follows the existing Scheduling action pattern: fail-soft/log rather than rollback/fail the workflow action.
  4. Private entries are redacted unless the actor is assigned or has user_schedule:update.
  5. Leave scheduling.assign_user event emission unchanged in this pass unless implementation discovers it blocks consistency.

Commands Run

pwd && git status --short && find ee/docs/plans -maxdepth 2 -type f | sed 's#^#/#' | head -80
rg -n "registerSchedulingActions|APPOINTMENT_|schedule|schedule_entries|schedule_entry|reschedule|NO_SHOW|appointment" shared ee server packages -g '!node_modules' -g '!**/.next/**'
find shared/workflow/runtime/actions/businessOperations -maxdepth 1 -type f -print
rg -n "publishWorkflowEvent|eventBus|createWorkflowEvent|SCHEDULE_ENTRY_|APPOINTMENT_CREATED|APPOINTMENT_RESCHEDULED|APPOINTMENT_ASSIGNED|APPOINTMENT_CANCELED|APPOINTMENT_COMPLETED|schedule_entries|schedule_entry_assignees|schedule_conflicts|recurr" shared server packages ee/server -g '!node_modules' -g '!**/.next/**'
rg -n "class ScheduleEntry|ScheduleEntry|scheduleEntry|schedule_entries|schedule_entry_assignees" server packages/scheduling shared -g '!node_modules' -g '!**/.next/**'
rg -n "scheduling\.assign_user|registerSchedulingActions|businessOperations" shared server ee packages -g '*test*' -g '!node_modules'
python3 /Users/roberisaacs/.codex/skills/alga-plan/scripts/scaffold_plan.py "Workflow Scheduling Actions" --slug workflow-scheduling-actions

Validation Commands for Plan

python3 /Users/roberisaacs/.codex/skills/alga-plan/scripts/validate_plan.py ee/docs/plans/2026-04-25-workflow-scheduling-actions

Likely Implementation Test Commands

Exact commands may change after implementation, but likely starting points are:

npm --prefix shared test -- workflow
npm --prefix server run test:integration -- scheduling
cd server && npx vitest run src/test/integration/scheduling/scheduleEntryRecurrence.integration.test.ts --coverage=false

Check package scripts before running broad test commands.

2026-04-25 — Post-main-merge Review

Merge State Observed

  • Current branch feature/workflows-scheduling-actions is at 1f44bf1b05, same as origin/main after PR #2400 (feature/workflow-clients-actions) was merged.
  • Our scheduling plan remains untracked in ee/docs/plans/2026-04-25-workflow-scheduling-actions/.
  • No scheduling implementation files have been changed yet in this branch.

Relevant New Main Additions Reviewed

  • shared/workflow/runtime/actions/businessOperations/clients.ts now contains a much richer client action implementation.
  • New client action tests provide useful patterns for scheduling:
    • shared/workflow/runtime/actions/__tests__/registerClientActionsMetadata.test.ts
    • shared/workflow/runtime/__tests__/workflowDesignerClientCatalogRuntime.test.ts
    • shared/workflow/runtime/actions/__tests__/businessOperations.clients.db.test.ts
    • shared/workflow/runtime/nodes/__tests__/actionCallClientSaveAsRuntime.test.ts
  • Client actions added a local publishWorkflowDomainEvent() helper that uses best-effort lazy import of @alga-psa/event-bus/publishers. This keeps shared-root tests stable while preserving runtime event publication behavior.
  • Main also added tenant-owned workflow definitions via server/migrations/20260425200000_add_tenant_id_to_workflow_definitions.cjs and updated shared/workflow/persistence/workflowDefinitionModelV2.ts to require tenant id for definition access.

Adjustments Made to This Scheduling Plan

  • Updated PRD to follow the Client action implementation/testing patterns:
    • picker metadata conversion tests via zodToWorkflowJsonSchema;
    • runtime catalog grouping tests from real registrations;
    • direct DB-backed action handler tests under the shared workflow runtime test tree;
    • runtime action.call + saveAs smoke coverage;
    • fail-soft lazy event publisher helper for APPOINTMENT_* events.
  • Updated PRD action contracts so entry_id inputs are non-empty strings rather than strict UUIDs. Rationale: existing recurrence virtual ids use <masterEntryId>_<timestamp>, so strict UUID schemas would block the requested recurrence-scope behavior.
  • Added F031/T017 for shared actor-resolution hardening against tenant-owned workflow_definitions. Current resolveRunActorUserId() joins workflow_definitions by workflow id only; implementation should join by run tenant as well before relying on schedule permission checks.
  • Added F032/T016 to explicitly align scheduling action tests with the new Client action/runtime patterns.
  • Updated T002 to reject blank entry refs but allow virtual recurring entry refs.

New Implementation Watch-outs

  • Do not copy clients.ts wholesale; use its patterns, not its domain assumptions.
  • Keep scheduling.assign_user unchanged per confirmed scope, but account for the fact that Client actions now have event-publication parity and scheduling lifecycle actions should too.
  • If adding a shared publisher helper to shared.ts, check whether moving the Client local helper is worth the extra scope. Current plan assumes a scheduling-local helper to avoid touching the merged Client action surface.
  • Add tests defensively: action registry is a singleton, so follow the Client tests' guard pattern (if (!registry.get(...)) register...) to avoid duplicate registration failures.

2026-04-26 — Implementation Completed

Skills Used / Coordination

  • Reviewed brainstorming skill requirements and intentionally skipped full brainstorm gate because this turn already provided a completed PRD + feature/test implementation checklist and explicitly requested autonomous execution of that plan.
  • Followed existing Client workflow action patterns for registration metadata, fail-soft event publishing, runtime catalog checks, and node smoke tests.

Implemented Code

  • Updated workflow actor resolution join to tenant-scope workflow definitions:
    • shared/workflow/runtime/actions/businessOperations/shared.ts
    • resolveRunActorUserId() now joins workflow_definitions on both workflow_id and wr.tenant_id = wd.tenant_id.
  • Replaced scheduling action runtime implementation with full lifecycle + read surface:
    • shared/workflow/runtime/actions/businessOperations/scheduling.ts
    • Added scheduling.find_entry, scheduling.search_entries, scheduling.reschedule, scheduling.reassign, scheduling.cancel, scheduling.complete.
    • Kept existing scheduling.assign_user behavior as-is.
    • Added private-entry redaction policy (redact unless actor assigned or has user_schedule:update; include_private_details controls full visibility return for find_entry).
    • Added read/write permission checks (user_schedule:read for read actions, user_schedule:update for lifecycle write actions).
    • Added recurrence-scope handling through ScheduleEntry.update(...) with virtual-occurrence anchor patches for single on virtual ids.
    • Added conflict detection helper for reschedule that excludes target series and ignores canceled/completed/no-show statuses.
    • Implemented conflict_mode behavior:
      • fail throws CONFLICT.
      • shift shifts to earliest non-conflicting slot preserving duration.
      • override persists and writes unresolved schedule_conflicts rows.
    • Added technician-role eligibility validation for reassignment.
    • Added no-op reassignment behavior with changed=false + event suppression.
    • Added write audits for all new mutating actions.
    • Added best-effort lazy publishWorkflowEvent helper and emitted:
      • APPOINTMENT_RESCHEDULED
      • APPOINTMENT_ASSIGNED (one per newly assigned user)
      • APPOINTMENT_CANCELED
      • APPOINTMENT_COMPLETED

Added Tests

  • Metadata/schema tests:
    • shared/workflow/runtime/actions/__tests__/registerSchedulingActionsMetadata.test.ts
  • Runtime designer grouping test:
    • shared/workflow/runtime/__tests__/workflowDesignerSchedulingCatalogRuntime.test.ts
  • Runtime node smoke (action.call + saveAs):
    • shared/workflow/runtime/nodes/__tests__/actionCallSchedulingSaveAsRuntime.test.ts
  • DB-backed scheduling action tests:
    • shared/workflow/runtime/actions/__tests__/businessOperations.scheduling.db.test.ts
  • Shared helper guard test for actor resolution:
    • shared/workflow/runtime/actions/__tests__/businessOperations.shared.actorResolution.db.test.ts
  • Shared DB test utility for runtime action DB suites:
    • shared/workflow/runtime/actions/__tests__/_dbTestUtils.ts

Final Decisions Confirmed in Implementation (F030)

  • Multi-assignee reassignment emits one APPOINTMENT_ASSIGNED per newly assigned user.
  • Cancel action permission uses user_schedule:update (status mutation, no row deletion).
  • Workflow event publishing is fail-soft and does not roll back successful table writes/audit writes.
  • Private entries are redacted unless actor is assigned or has user_schedule:update.
  • scheduling.assign_user event behavior remains unchanged in this pass.

Validation Commands Run

cd shared && npx vitest run --coverage.enabled=false \
  workflow/runtime/actions/__tests__/registerSchedulingActionsMetadata.test.ts \
  workflow/runtime/__tests__/workflowDesignerSchedulingCatalogRuntime.test.ts \
  workflow/runtime/nodes/__tests__/actionCallSchedulingSaveAsRuntime.test.ts \
  workflow/runtime/actions/__tests__/businessOperations.scheduling.db.test.ts

cd shared && npx vitest run --coverage.enabled=false \
  workflow/runtime/actions/__tests__/businessOperations.shared.actorResolution.db.test.ts

cd server && npx vitest run --coverage.enabled=false \
  src/test/integration/scheduling/scheduleEntryRecurrence.integration.test.ts

Gotchas / Notes

  • @alga-psa/scheduling/models/scheduleEntry was not export-resolvable under shared Vitest import-analysis; switched to direct monorepo source import path for runtime/test code in this branch.
  • Vitest coverage config mismatch in this environment required explicit --coverage.enabled=false.
  • DB-backed runtime tests are intentionally using mocked withTenantTransaction/requirePermission wrappers for deterministic actor + permission simulation while still exercising real table mutations.