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

64 KiB
Raw Blame History

SCRATCHPAD — Workflow Event Catalog v2 Domain Events

Rolling notes for this plan: discoveries, decisions, links, commands, and gotchas.

Source Material

  • Event proposals inventory: ee/docs/plans/2025-12-28-workflow-event-catalog/event-proposals.md
  • Existing Events Catalog v2 plan: ee/docs/plans/2025-12-28-workflow-event-catalog/PRD.md
  • This plan: ee/docs/plans/2026-01-23-workflow-event-catalog-domain-events/PRD.md
  • Features/tests: ee/docs/plans/2026-01-23-workflow-event-catalog-domain-events/features.json and ee/docs/plans/2026-01-23-workflow-event-catalog-domain-events/tests.json

Key Discovery: Event Bus Already Has a Large Event Surface

shared/workflow/streams/eventBusSchema.ts already defines many event types beyond what exists in system_event_catalog migrations, including:

  • Ticket comment events (TICKET_COMMENT_ADDED, TICKET_COMMENT_UPDATED)
  • Scheduling events (SCHEDULE_ENTRY_CREATED, SCHEDULE_ENTRY_UPDATED, SCHEDULE_ENTRY_DELETED)
  • Accounting export and calendar sync events
  • Document mention event (USER_MENTIONED_IN_DOCUMENT)
  • RMM events (RMM_*)

Implication: adding events is not only “add catalog rows”; we must keep catalog, event bus schema/types, and workflow schema registry refs aligned.

Key Discovery: Schema Registry (Workflow Runtime v2) Is Separate From Event Bus Zod Schemas

Workflow runtime v2 registers payload schemas in shared/workflow/runtime/init.ts (schema registry keys like payload.TicketCreated.v1).

Event bus has its own Zod payload schemas in shared/workflow/streams/eventBusSchema.ts.

Implication: for each new catalog entry we want form-mode simulation and trigger mapping, we likely need:

  • payload_schema_ref → schema registry registration (payload.*.v1)
  • and (if the event is published through the event bus) inclusion in EventTypeEnum + EventPayloadSchemas mapping.

Current Event Publishing Path (observed)

  • Workflow v2 simulation / submission path uses: packages/workflows/src/actions/workflow-event-actions.ts (submitWorkflowEventAction) which publishes to the event bus and validates event_type exists in catalog tables.
  • There are multiple non-workflow publishers already in the repo (examples found via ripgrep):
    • packages/billing/src/services/accountingExportService.ts
    • packages/integrations/src/services/calendar/CalendarSyncService.ts
    • server/src/services/email/EmailProcessor.ts (publishes INBOUND_EMAIL_RECEIVED)

Implication: we should standardize on @alga-psa/event-bus/publishers helpers for new emissions, and ensure payload shapes match both schemas.

Decisions Needed (Blocking)

  1. Ticket messages vs ticket comments
    • Keep existing TICKET_COMMENT_* as canonical, and map proposed TICKET_MESSAGE_* onto those semantics?
    • Or introduce TICKET_MESSAGE_* and deprecate TICKET_COMMENT_*?
    • Decision (2026-01-23): Introduce TICKET_MESSAGE_* as canonical workflow v2 domain events; keep TICKET_COMMENT_* as legacy/back-compat (no automatic aliasing due to payload shape differences).
  2. Scheduling appointments vs schedule entries
    • Reuse existing SCHEDULE_ENTRY_* as canonical?
    • Or introduce APPOINTMENT_* (and decide whether a schedule entry is an appointment)?
    • Decision (2026-01-23): Introduce APPOINTMENT_* as canonical workflow triggers; keep SCHEDULE_ENTRY_* for lower-level schedule entry changes and existing integrations.
  3. Client/company identifiers
    • Standardize payloads on clientId, companyId, or include both?
  4. Payload casing
    • Decision (2026-01-23): use camelCase payload keys for all new payload.*.v1 schemas and event bus payloads (documented in ee/docs/plans/2026-01-23-workflow-event-catalog-domain-events/PRD.md §6.2).
  5. Catalog-only events
    • Which of SLA/approvals/dispatch/signatures are expected to be emitted now vs later?

Work Log

  • 2026-01-23: Completed F001 (payload conventions) by expanding PRD.md §6.2 with:

    • camelCase key requirement
    • required tenantId + occurredAt
    • actor field guidance (actorUserId / actorContactId / optional actorType)
    • transition and mutation conventions (previousX/newX, updatedFields, changes)
  • 2026-01-23: Completed F002 (shared publishing helpers + hooks):

    • Added payload enrichment helper shared/workflow/streams/workflowEventPublishHelpers.ts:
      • ensures tenantId and occurredAt
      • enriches actor fields (actorType, actorUserId, actorContactId)
      • carries optional idempotencyKey
    • Added publish-time hooks for workflow stream conversion:
      • shared/workflow/streams/eventBusSchema.ts now supports WorkflowPublishHooks and convertToWorkflowEvent(event, hooks)
      • workflow user_id now prefers payload.actorUserId (falls back to payload.userId)
    • Wired hooks through both event bus implementations:
      • packages/event-bus/src/eventBus.ts and server/src/lib/eventBus/index.ts accept { workflow } publish options
    • Added convenience publisher wrappers:
      • packages/event-bus/src/publishers/index.ts and server/src/lib/eventBus/publishers/index.ts export publishWorkflowEvent(...)
  • 2026-01-23: Completed F003 (schema registry payload schemas + registrations):

    • Added shared payload building blocks in shared/workflow/runtime/schemas/commonEventPayloadSchemas.ts:
      • tenantId + occurredAt required on all new payload schemas
      • optional actor fields (actorUserId, actorContactId, actorType)
      • shared updatedFields[] and changes{path:{previous,new}} helpers
    • Added payload.*.v1 Zod payload schemas for every event in event-proposals.md:
      • tickets: shared/workflow/runtime/schemas/ticketEventSchemas.ts
      • scheduling: shared/workflow/runtime/schemas/schedulingEventSchemas.ts
      • projects: shared/workflow/runtime/schemas/projectEventSchemas.ts
      • billing: shared/workflow/runtime/schemas/billingEventSchemas.ts
      • CRM/tags: shared/workflow/runtime/schemas/crmEventSchemas.ts
      • documents: shared/workflow/runtime/schemas/documentEventSchemas.ts
      • email/notifications/surveys: shared/workflow/runtime/schemas/communicationsEventSchemas.ts
      • integrations: shared/workflow/runtime/schemas/integrationEventSchemas.ts
      • assets/media: shared/workflow/runtime/schemas/assetMediaEventSchemas.ts
      • company: shared/workflow/runtime/schemas/companyEventSchemas.ts
    • Centralized schema registrations in shared/workflow/runtime/schemas/workflowEventPayloadSchemas.ts and updated shared/workflow/runtime/init.ts to register the full map at init.
    • Verified registry coverage: all payload.<PascalCase>.v1 refs implied by event-proposals.md are present in workflowEventPayloadSchemas.
  • 2026-01-23: Completed F004 (event bus schema/types + overlap resolution):

    • Expanded shared/workflow/streams/eventBusSchema.ts:
      • added all proposed domain EventTypeEnum values from event-proposals.md
      • mapped new domain event types to workflow runtime payload schemas (camelCase + occurredAt)
      • kept legacy event payloads working via Zod unions for overlapping “already present” events
      • added a runtime guard to ensure every EVENT_TYPES entry has a payload schema
    • Removed duplicate schema/type definitions by re-exporting shared schema from:
      • packages/event-bus/src/events.ts
      • server/src/lib/eventBus/events.ts
    • Aligned catalog typing with the shared event bus enum:
      • shared/workflow/types/eventCatalog.ts
    • Added focused test coverage:
      • shared/workflow/streams/__tests__/eventBusSchema.expandedEvents.test.ts
    • Documented overlap decisions in ee/docs/plans/2026-01-23-workflow-event-catalog-domain-events/PRD.md §10.
  • 2026-01-23: Completed F005 (catalog migration upsert for expanded domain events):

    • Added idempotent system catalog upsert migration: server/migrations/20260123150000_upsert_domain_workflow_event_catalog_v2.cjs
      • Upserts 134 total events into system_event_catalog:
        • 122 proposed events parsed from ee/docs/plans/2025-12-28-workflow-event-catalog/event-proposals.md
        • 12 “already present” core events (Company/Ticket/Project/Invoice/Email provider)
      • Normalizes name, description, category, and payload_schema_ref for each row.
      • Sets payload_schema_ref as payload.<PascalCase(event_type)>.v1 (e.g. INBOUND_EMAIL_RECEIVEDpayload.InboundEmailReceived.v1).
      • Intentionally does not manage legacy payload_schema JSON in catalog rows (workflow v2 uses schema registry via payload_schema_ref).
  • 2026-01-23: Completed F006 (simulation uses payload_schema_ref + schema validation):

    • Server-side event ingestion now enriches simulator payloads and validates them against the schema registry:
      • server/src/lib/actions/workflow-runtime-v2-actions.ts now:
        • derives sourcePayloadSchemaRef from submission override or catalog payload_schema_ref
        • enriches payload via buildWorkflowPayload(...) (adds tenantId, occurredAt, actorUserId, actorType)
        • rejects invalid payloads with 400 including Zod issues, and records an error workflow_runtime_event
    • Added schema registry coverage tests (no DB required):
      • shared/workflow/runtime/__tests__/workflowEventSimulatorSchemas.test.ts
    • Attempted DB-backed integration test run, but this sandbox cannot connect to localhost Postgres (EPERM); kept verification to schema-level tests.
  • 2026-01-23: Completed F010 (ticket transition emission):

    • Emitted workflow v2 domain events from real ticket update paths:
      • packages/tickets/src/actions/ticketActions.ts now publishes: TICKET_STATUS_CHANGED, TICKET_PRIORITY_CHANGED, TICKET_UNASSIGNED, TICKET_REOPENED, TICKET_ESCALATED, TICKET_QUEUE_CHANGED via publishWorkflowEvent(...) and shared transition detection.
      • packages/tickets/src/actions/optimizedTicketActions.ts updated similarly (this is the main cached ticket update path).
    • Upgraded legacy ticket lifecycle emissions to include workflow v2 context:
      • TICKET_UPDATED, TICKET_ASSIGNED, TICKET_CLOSED now emit via publishWorkflowEvent(...) (adds occurredAt + actorUserId), and TICKET_ASSIGNED includes previousAssignee*/newAssignee* fields for domain-style payloads while keeping legacy userId.
    • Added a pure transition builder for testability: packages/tickets/src/lib/workflowTicketTransitionEvents.ts.
    • Extended ticket zod schemas to include ITIL escalation fields (present in DB): packages/tickets/src/schemas/ticket.schema.ts.
    • Added unit coverage for transition detection: packages/tickets/src/lib/__tests__/workflowTicketTransitionEvents.test.ts (runs without DB).
  • 2026-01-23: Completed F011 (ticket relationship/aggregation emission):

    • Implemented TICKET_MERGED / TICKET_SPLIT emissions via ticket bundling actions:
      • packages/tickets/src/actions/ticketBundleActions.ts now publishes:
        • TICKET_MERGED when child tickets are attached to a master bundle (bundle/add children/promote master).
        • TICKET_SPLIT when child tickets are detached (remove child/unbundle master).
      • Semantics note: these events represent bundle attach/detach in todays product (there is no true destructive “merge tickets” feature yet); payload still follows proposed schema (sourceTicketId/targetTicketId, originalTicketId/newTicketIds) and includes reason for workflow authors.
    • Updated bundling integration test harness to mock @alga-psa/event-bus/publishers to keep DB-backed tests isolated from the event bus.
  • 2026-01-23: Completed F012 (ticket communication emission):

    • Added a shared builder for ticket communication domain events:
      • packages/tickets/src/lib/workflowTicketCommunicationEvents.ts builds additive TICKET_MESSAGE_ADDED plus:
        • TICKET_INTERNAL_NOTE_ADDED for internal visibility
        • TICKET_CUSTOMER_REPLIED when a contact id is available
    • Emitted these events from real comment creation paths (while keeping legacy TICKET_COMMENT_* unchanged):
      • packages/tickets/src/actions/comment-actions/commentActions.ts (primary comment creation flow)
      • packages/tickets/src/actions/ticketActions.ts and packages/tickets/src/actions/optimizedTicketActions.ts (legacy/server action flows)
    • Added unit coverage with schema validation via buildWorkflowPayload(...):
      • packages/tickets/src/lib/__tests__/workflowTicketCommunicationEvents.test.ts
  • 2026-01-23: Completed F013 (ticket work tracking emission):

    • Emitted TICKET_TIME_ENTRY_ADDED when time entries are created for work_item_type='ticket':
      • server/src/lib/api/services/TimeEntryService.ts publishes on both direct create (create) and time-tracking completion (stopTimeTracking).
    • Added a small pure builder for publish-time payload shaping:
      • server/src/lib/api/services/timeEntryWorkflowEvents.ts
    • Added unit coverage that validates the built payload against payload.TicketTimeEntryAdded.v1 via buildWorkflowPayload(...):
      • server/src/test/unit/timeEntryWorkflowEvents.test.ts
  • 2026-01-23: Completed F014 (ticket SLA stage emission):

    • Implemented ITIL-backed “resolution SLA” stage events when itil_priority_level is present:
      • TICKET_SLA_STAGE_ENTERED emitted on ticket create in:
        • packages/tickets/src/actions/ticketActions.ts (addTicket, createTicketFromAsset)
      • TICKET_SLA_STAGE_MET / TICKET_SLA_STAGE_BREACHED emitted on ticket close in:
        • packages/tickets/src/actions/ticketActions.ts (updateTicket)
        • packages/tickets/src/actions/optimizedTicketActions.ts (updateTicketWithCache)
    • Added pure builders and idempotency keys:
      • packages/tickets/src/lib/workflowTicketSlaStageEvents.ts
    • Notes/constraints:
      • Stage emitted is currently resolution only (no first-class response/custom stage model in product yet).
      • Uses tenantId as a stable slaPolicyId placeholder until a real SLA policy model exists.
  • 2026-01-23: Completed F015 (ticket approvals — catalog-only):

    • Marked TICKET_APPROVAL_REQUESTED / TICKET_APPROVAL_GRANTED / TICKET_APPROVAL_REJECTED as catalog-only for now (no ticket approval subsystem exists to emit these events from).
    • Documented the decision in ee/docs/plans/2026-01-23-workflow-event-catalog-domain-events/PRD.md (Open Questions #4).
  • 2026-01-23: Completed F020 (appointment lifecycle/assignment emission):

    • Emitted workflow v2 appointment domain events from real scheduling/appointment flows:
      • packages/scheduling/src/actions/scheduleActions.ts emits APPOINTMENT_CREATED, APPOINTMENT_RESCHEDULED, APPOINTMENT_CANCELED, APPOINTMENT_COMPLETED, APPOINTMENT_NO_SHOW, APPOINTMENT_ASSIGNED when schedule entries represent appointments (work_item_type='appointment_request' or ticket).
      • packages/client-portal/src/actions/client-portal-actions/appointmentRequestActions.ts emits APPOINTMENT_CREATED, APPOINTMENT_RESCHEDULED, APPOINTMENT_CANCELED, APPOINTMENT_ASSIGNED for client portal appointment requests (actor is CONTACT).
      • packages/scheduling/src/actions/appointmentRequestManagementActions.ts emits APPOINTMENT_CREATED/APPOINTMENT_ASSIGNED for the legacy “approve request creates schedule entry” fallback; emits APPOINTMENT_ASSIGNED on reassignment during approval.
    • Added shared payload builders:
      • shared/workflow/streams/domainEventBuilders/appointmentEventBuilders.ts
    • Added schema-compat unit test coverage:
      • shared/workflow/streams/domainEventBuilders/__tests__/appointmentEventBuilders.test.ts
    • Notes/constraints:
      • timezone is emitted as UTC (schedule UI and storage treat times as UTC today).
      • APPOINTMENT_NO_SHOW is emitted only when a schedule entry status is set to a no-show variant; party defaults to customer until the product captures who no-showed explicitly.
  • 2026-01-23: Completed F021 (schedule block create/delete emission):

    • Implemented SCHEDULE_BLOCK_CREATED / SCHEDULE_BLOCK_DELETED emission for “availability blocks” represented today as private ad-hoc schedule entries:
      • is_private=true, work_item_type='ad_hoc', work_item_id=null, exactly one assigned_user_ids owner.
    • Added shared payload builders:
      • shared/workflow/streams/domainEventBuilders/scheduleBlockEventBuilders.ts
    • Emitted events from scheduling actions:
      • packages/scheduling/src/actions/scheduleActions.ts publishes on create/delete, and on update when an entry becomes/ceases a private ad-hoc block.
    • Added schema-compat unit test coverage:
      • shared/workflow/streams/domainEventBuilders/__tests__/scheduleBlockEventBuilders.test.ts
    • Notes/constraints:
      • timezone is emitted as UTC.
      • No “block updated” domain event exists yet; time changes to an existing block do not emit a domain event unless the entry crosses the block/non-block boundary.
  • 2026-01-23: Completed F022 (capacity threshold emission):

    • Emitted CAPACITY_THRESHOLD_REACHED from real scheduling mutations:
      • packages/scheduling/src/actions/scheduleActions.ts publishes after create/update/delete via maybePublishCapacityThresholdReached(...).
    • Implemented team/day capacity math (UTC date) based on existing data model:
      • Capacity limit = sum of resources.max_daily_capacity for active team members.
      • Current booked = sum of schedule-entry overlap hours per assignee for that team/date.
      • Event emits only on a threshold crossing (previousBooked < limit && currentBooked >= limit).
    • Added shared payload builder + unit coverage:
      • shared/workflow/streams/domainEventBuilders/capacityThresholdEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/capacityThresholdEventBuilders.test.ts
    • Added scheduling unit coverage for threshold/date math:
      • packages/scheduling/src/lib/__tests__/capacityThresholdWorkflowEvents.test.ts
  • 2026-01-23: Completed F023 (technician dispatch lifecycle emission):

    • Added shared payload/status helpers:
      • shared/workflow/streams/domainEventBuilders/technicianDispatchEventBuilders.ts
    • Emitted dispatch lifecycle workflow events from real scheduling updates:
      • packages/scheduling/src/actions/scheduleActions.ts now publishes:
        • TECHNICIAN_DISPATCHED when technicians are newly assigned to a ticket/appointment schedule entry
        • TECHNICIAN_EN_ROUTE / TECHNICIAN_ARRIVED when schedule entry status transitions to an en-route / arrived value (case-insensitive, supports common variants)
        • TECHNICIAN_CHECKED_OUT when schedule entry status transitions to a checked-out value or when the appointment is marked completed
    • Added schema-compat unit test coverage:
      • shared/workflow/streams/domainEventBuilders/__tests__/technicianDispatchEventBuilders.test.ts
  • 2026-01-23: Completed F030 (project lifecycle emission):

    • Added shared payload builders:
      • shared/workflow/streams/domainEventBuilders/projectLifecycleEventBuilders.ts
    • Emitted domain events from real project update paths:
      • packages/projects/src/actions/projectActions.ts publishes PROJECT_UPDATED (with updatedFields + {previous,new} changes) and PROJECT_STATUS_CHANGED on status transitions via publishWorkflowEvent(...).
      • server/src/lib/api/services/ProjectService.ts publishes the same domain events for REST API updates.
    • Kept legacy email notifications working with the new payload shape:
      • server/src/lib/eventBus/subscribers/projectEmailSubscriber.ts now supports both legacy { changes: Record<string, unknown> } and domain { changes: Record<string, {previous,new}> } shapes and uses actorUserId when userId is absent.
    • Added schema-compat unit test coverage:
      • shared/workflow/streams/domainEventBuilders/__tests__/projectLifecycleEventBuilders.test.ts
    • Cleanup: removed forbidden feature-to-feature import (projects → clients) by querying contacts directly in packages/projects/src/actions/projectActions.ts.
  • 2026-01-23: Completed F031 (project task lifecycle emission):

    • Added shared payload builders + schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/projectTaskEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/projectTaskEventBuilders.test.ts
    • Emitted workflow v2 domain events from real task creation/update paths:
      • packages/projects/src/actions/projectTaskActions.ts publishes:
        • PROJECT_TASK_CREATED on task create/duplicate
        • PROJECT_TASK_ASSIGNED on primary assignee changes (domain payload with assignedToId/assignedToType)
        • PROJECT_TASK_STATUS_CHANGED + PROJECT_TASK_COMPLETED when status mapping transitions (completed = enters a closed status)
      • packages/projects/src/actions/phaseTaskImportActions.ts publishes PROJECT_TASK_CREATED (+ PROJECT_TASK_ASSIGNED when assigned) for imported tasks.
      • server/src/lib/api/services/ProjectService.ts publishes the same task lifecycle events for REST API create/update paths.
      • server/src/lib/models/scheduleEntry.ts now publishes domain-shaped PROJECT_TASK_ASSIGNED when schedule-driven dispatch assigns a task.
    • Updated legacy notification/email subscribers to tolerate domain payloads:
      • server/src/lib/eventBus/subscribers/internalNotificationSubscriber.ts (PROJECT_TASK_ASSIGNED)
      • server/src/lib/eventBus/subscribers/projectEmailSubscriber.ts (PROJECT_TASK_ASSIGNED)
    • Verification:
      • npx vitest run shared/workflow/streams/domainEventBuilders/__tests__/projectTaskEventBuilders.test.ts
  • 2026-01-23: Completed F032 (project dependency events emission):

    • Added shared payload builders + schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/projectTaskEventBuilders.ts (dependency blocked/unblocked payload builders)
      • shared/workflow/streams/domainEventBuilders/__tests__/projectTaskEventBuilders.test.ts
    • Emitted dependency lifecycle workflow events from real dependency CRUD paths:
      • packages/projects/src/actions/projectTaskActions.ts publishes:
        • PROJECT_TASK_DEPENDENCY_BLOCKED when a blocking relationship is created (dependency_type in {blocks,blocked_by})
        • PROJECT_TASK_DEPENDENCY_UNBLOCKED when a blocking relationship is removed
    • Notes/constraints:
      • Only blocking relationships emit events; related_to dependencies remain non-workflow.
  • 2026-01-23: Completed F033 (project approvals — catalog-only):

    • Discovery: there is no authoritative “project approval request/decision” subsystem today (no project approval model/service/actions to hook).
    • Decision: treat PROJECT_APPROVAL_* events as catalog-only (mirrors TICKET_APPROVAL_* rationale) until a real project approval feature exists.
    • Updated ee/docs/plans/2026-01-23-workflow-event-catalog-domain-events/PRD.md Open Questions #4 to reflect PROJECT_APPROVAL_* catalog-only status.
  • 2026-01-23: Completed F040 (invoice lifecycle emission):

    • Added billing workflow payload builders:
      • server/src/lib/api/services/invoiceWorkflowEvents.ts
    • Emitted invoice lifecycle workflow v2 domain events from real invoice operations:
      • server/src/lib/api/services/InvoiceService.ts now publishes:
        • INVOICE_SENT on sendInvoice (delivery method inferred; includes clientId, sentByUserId, sentAt)
        • INVOICE_STATUS_CHANGED on all invoice status transitions across update, finalizeInvoice, sendInvoice, recordPayment, applyCredit, recordRefund, bulkUpdateStatus, and soft-cancel in delete
        • INVOICE_DUE_DATE_CHANGED from update when due date changes
        • INVOICE_OVERDUE when status transitions into overdue (amount due computed from payments + credits)
        • INVOICE_WRITTEN_OFF when transitioning overduecancelled with a remaining balance (maps “write off” onto todays available status model)
    • Added schema-compat unit coverage:
      • server/src/test/unit/invoiceWorkflowEvents.test.ts
  • 2026-01-23: Completed F041 (payment events emission):

    • Added billing workflow payload builders:
      • server/src/lib/api/services/paymentWorkflowEvents.ts
    • Emitted workflow v2 payment domain events from real payment paths:
      • Manual invoice payments/refunds in server/src/lib/api/services/InvoiceService.ts now publish:
        • PAYMENT_RECORDED + PAYMENT_APPLIED on recordPayment
        • PAYMENT_REFUNDED on recordRefund
      • Stripe webhook processing in ee/server/src/lib/payments/PaymentService.ts now publishes:
        • PAYMENT_RECORDED + PAYMENT_APPLIED on successful payment recording
        • PAYMENT_REFUNDED on refund recording
        • PAYMENT_FAILED on payment_intent.payment_failed events (when amount is present)
    • Added schema-compat unit coverage:
      • server/src/test/unit/paymentWorkflowEvents.test.ts
    • Verification:
      • npx vitest run server/src/test/unit/paymentWorkflowEvents.test.ts
  • 2026-01-23: Completed F042 (credit events emission):

    • Added shared billing payload builders + schema-compat tests:
      • shared/workflow/streams/domainEventBuilders/creditNoteEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/creditNoteEventBuilders.test.ts
    • Emitted workflow v2 credit note domain events from real credit flows:
      • packages/billing/src/actions/creditActions.ts now publishes:
        • CREDIT_NOTE_CREATED when prepayment credit tracking entries are created
        • CREDIT_NOTE_APPLIED per credit source used when applying credits to an invoice (supports multi-credit allocation)
      • packages/billing/src/actions/invoiceModification.ts now publishes:
        • CREDIT_NOTE_CREATED when finalizing a negative invoice that issues credit (credit_issuance_from_negative_invoice)
        • CREDIT_NOTE_VOIDED when hard-deleting a negative invoice that issued unused credit (maps delete → void)
    • Verification:
      • npx vitest run shared/workflow/streams/domainEventBuilders/__tests__/creditNoteEventBuilders.test.ts
  • 2026-01-23: Completed F043 (contract events emission):

    • Added shared contract payload builders + renewal timing helper:
      • shared/workflow/streams/domainEventBuilders/contractEventBuilders.ts
      • Renewal “upcoming” window default: 30 days (computeContractRenewalUpcoming)
    • Emitted workflow v2 contract domain events from real client-contract paths:
      • packages/clients/src/actions/clientContractActions.ts publishes:
        • CONTRACT_CREATED on assignContractToClient and createClientContract
        • CONTRACT_UPDATED + CONTRACT_STATUS_CHANGED + CONTRACT_RENEWAL_UPCOMING (on threshold crossing) on updateClientContract
        • CONTRACT_STATUS_CHANGED on deactivateClientContract
      • packages/billing/src/actions/contractWizardActions.ts publishes:
        • CONTRACT_CREATED (+ optional CONTRACT_RENEWAL_UPCOMING) on createClientContractFromWizard
    • Added schema-compat unit test coverage:
      • shared/workflow/streams/domainEventBuilders/__tests__/contractEventBuilders.test.ts
  • 2026-01-23: Completed F044 (recurring billing run events emission):

    • Added shared payload builders + schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/recurringBillingRunEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/recurringBillingRunEventBuilders.test.ts
    • Implemented a billing “run” action that publishes workflow v2 events around invoice generation:
      • packages/billing/src/actions/recurringBillingRunActions.ts publishes RECURRING_BILLING_RUN_STARTED / COMPLETED (with invoicesCreated, failedCount) and FAILED for unexpected fatal failures.
    • Updated billing dashboard invoice generation flows to use the run action (batch + preview generation):
      • packages/billing/src/components/billing-dashboard/AutomaticInvoices.tsx
    • Verification:
      • npx vitest run shared/workflow/streams/domainEventBuilders/__tests__/recurringBillingRunEventBuilders.test.ts
      • Note: npx tsc -p packages/billing/tsconfig.json --noEmit currently fails due to existing TS errors in packages/billing/src/actions/contractWizardActions.ts and packages/billing/src/actions/creditActions.ts (pre-existing; not addressed in this item).
  • 2026-01-23: Completed F050 (CRM client events emission):

    • Added shared payload builders:
      • shared/workflow/streams/domainEventBuilders/clientEventBuilders.ts
    • Emitted workflow v2 domain events from real client create/update/archive paths:
      • packages/clients/src/actions/clientActions.ts now publishes:
        • CLIENT_CREATED on create
        • CLIENT_UPDATED on update (with updatedFields + {previous,new} changes)
        • CLIENT_STATUS_CHANGED when properties.status transitions
        • CLIENT_OWNER_ASSIGNED when account_manager_id changes to a user
        • CLIENT_ARCHIVED when is_inactive transitions to true (including markClientInactiveWithContacts)
      • server/src/lib/api/services/ClientService.ts now publishes the same domain events for REST API create/update paths.
    • Added schema-compat unit test coverage:
      • shared/workflow/streams/domainEventBuilders/__tests__/clientEventBuilders.test.ts
    • Notes/constraints:
      • CLIENT_MERGED remains catalog-only for now (no authoritative client merge/consolidate path exists to emit from today).
  • 2026-01-23: Completed F051 (CRM contact events emission):

    • Added shared payload builders + schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/contactEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/contactEventBuilders.test.ts
    • Emitted workflow v2 contact domain events from real contact create/update/archive paths:
      • packages/clients/src/actions/contact-actions/contactActions.tsx publishes CONTACT_CREATED, CONTACT_UPDATED, CONTACT_ARCHIVED (when is_inactive transitions false → true) via publishWorkflowEvent(...).
      • server/src/lib/api/services/ContactService.ts publishes the same domain events for REST API create/update paths.
    • Implemented CONTACT_PRIMARY_SET emission from the authoritative client billing contact change:
      • packages/clients/src/actions/clientActions.ts publishes when billing_contact_id changes to a non-empty contact id.
      • server/src/lib/api/services/ClientService.ts publishes the same event for REST API updates.
    • Notes/constraints:
      • CONTACT_MERGED remains catalog-only for now (no authoritative “merge contacts” path exists in product code today).
  • 2026-01-23: Completed F052 (CRM interaction/note events emission):

    • Added shared payload builders + schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/crmInteractionNoteEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/crmInteractionNoteEventBuilders.test.ts
    • Emitted workflow v2 domain events from real CRM activity paths:
      • packages/clients/src/actions/interactionActions.ts publishes INTERACTION_LOGGED on interaction creation (derives channel from interaction type; ensures interactions are linked to a clientId).
      • packages/clients/src/actions/clientNoteActions.ts publishes NOTE_CREATED when a notes document is first created/linked to a client.
      • packages/clients/src/actions/contact-actions/contactNoteActions.ts publishes NOTE_CREATED when a notes document is first created/linked to a contact.
  • 2026-01-23: Completed F053 (tags events emission):

    • Added shared payload builders + schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/tagEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/tagEventBuilders.test.ts
    • Emitted workflow v2 tag domain events from tag actions:
      • packages/tags/src/actions/tagActions.ts publishes TAG_DEFINITION_CREATED, TAG_DEFINITION_UPDATED, TAG_APPLIED, TAG_REMOVED (uses definition id as tagId, and entityType/entityId from tagged_type/tagged_id).
    • Added getOrCreateWithStatus to detect definition creates reliably:
      • packages/tags/src/models/tagDefinition.ts
  • 2026-01-23: Completed F060 (document storage lifecycle emission):

    • Added shared payload builders:
      • shared/workflow/streams/domainEventBuilders/documentStorageEventBuilders.ts
    • Emitted workflow v2 document storage domain events from the authoritative storage service:
      • packages/documents/src/storage/StorageService.ts publishes:
        • DOCUMENT_UPLOADED after file record creation (includes documentId, fileName, contentType, sizeBytes, storageKey)
        • DOCUMENT_DELETED after storage delete + soft delete (includes documentId, deletedAt)
    • Verification:
      • npx tsc -p packages/documents/tsconfig.json --noEmit
      • npx vitest run packages/documents/tests/storageConfig.test.ts
    • Notes/constraints:
      • documentId maps to the file storage record id (external_files.file_id) today.
      • Publishing is best-effort (errors are logged but do not fail the upload/delete).
  • 2026-01-24: Completed F061 (document association/detach emission):

    • Added shared payload builders + schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/documentAssociationEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/documentAssociationEventBuilders.test.ts
    • Emitted workflow v2 document association events from real document flows:
      • packages/documents/src/actions/documentActions.ts publishes:
        • DOCUMENT_ASSOCIATED on association creation (bulk associate and upload-time associations; also client/contract-specific helpers)
        • DOCUMENT_DETACHED on manual detach and on document deletion (reason=document_deleted)
      • packages/documents/src/actions/documentBlockContentActions.ts publishes DOCUMENT_ASSOCIATED when a block-document is created and linked to an entity
    • Verification:
      • npx vitest run shared/workflow/streams/domainEventBuilders/__tests__/documentAssociationEventBuilders.test.ts
  • 2026-01-24: Completed F062 (document generation emission):

    • Added shared payload builder + schema-compat unit test:
      • shared/workflow/streams/domainEventBuilders/documentGeneratedEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/documentGeneratedEventBuilders.test.ts
    • Emitted DOCUMENT_GENERATED from the authoritative PDF generation path (covers invoice PDFs and document-to-PDF conversion):
      • server/src/services/pdf-generation.service.ts publishes DOCUMENT_GENERATED after storing the generated PDF in external_files
      • sourceType is invoice when generating an invoice PDF, otherwise document
      • sourceId is the invoice id / source document id
    • Notes/constraints:
      • Publishing is best-effort (errors are logged but do not fail PDF generation/storage).
  • 2026-01-24: Completed F063 (document signatures — catalog-only):

    • Discovery: there is no authoritative “document signature request/fulfillment” subsystem today (no signature request model/service/actions and no provider callback/webhook ingestion path to hook).
    • Decision: treat DOCUMENT_SIGNATURE_REQUESTED, DOCUMENT_SIGNED, and DOCUMENT_SIGNATURE_EXPIRED as catalog-only until an e-sign feature is implemented.
    • Updated ee/docs/plans/2026-01-23-workflow-event-catalog-domain-events/PRD.md Open Questions #4 to record the decision.
  • 2026-01-24: Completed F070 (inbound email reply emission):

    • Emitted INBOUND_EMAIL_REPLY_RECEIVED when the system email processing workflow matches an inbound email to an existing ticket and successfully creates the ticket comment.
    • Implemented best-effort publishing from shared/workflow/actions/emailWorkflowActions.ts (createCommentFromEmail) behind an optional inboundReplyEvent input.
    • Updated both workflow definitions to pass matchedBy (reply_token vs thread_headers) and normalized receivedAt for action schema validation:
      • shared/workflow/workflows/system-email-processing-workflow.ts (+ .generated.js)
      • services/workflow-worker/src/workflows/system-email-processing-workflow.ts (+ .generated.js)
    • Added a domain payload builder + schema-compat unit test:
      • shared/workflow/streams/domainEventBuilders/inboundEmailReplyEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/inboundEmailReplyEventBuilders.test.ts
  • 2026-01-24: Completed F071 (outbound email lifecycle emission):

    • Emitted workflow v2 outbound lifecycle events from the authoritative outbound send abstraction:
      • server/src/lib/email/BaseEmailService.ts now publishes best-effort:
        • OUTBOUND_EMAIL_QUEUED immediately before provider send attempt
        • OUTBOUND_EMAIL_SENT or OUTBOUND_EMAIL_FAILED after provider result
    • Notes/constraints:
      • Uses a generated workflow messageId (UUID) per send attempt, and includes provider message id when available.
      • Publishing failures do not block email delivery (events are best-effort).
    • Added unit coverage:
      • server/src/test/unit/outboundEmailWorkflowEvents.test.ts
  • 2026-01-24: Completed F072 (email delivery/feedback emission):

    • Implemented Resend delivery/feedback webhook ingestion (Svix signature verification):
      • server/src/app/api/email/webhooks/resend/route.ts
      • server/src/services/email/webhooks/resendWebhookEvents.ts (signature verify + mapping)
    • Added correlation tags/headers so provider callbacks can map back to tenantId + workflow messageId:
      • server/src/lib/email/BaseEmailService.ts now sets X-Alga-Workflow-Message-Id/X-Alga-Tenant-Id headers and alga_* tags on provider messages.
    • Emits workflow v2 domain events (best-effort) when Resend provider support exists:
      • EMAIL_DELIVERED, EMAIL_BOUNCED, EMAIL_COMPLAINT_RECEIVED, EMAIL_UNSUBSCRIBED
    • Added schema-compat unit coverage:
      • shared/workflow/streams/domainEventBuilders/emailFeedbackEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/emailFeedbackEventBuilders.test.ts
      • server/src/test/unit/resendWebhookEvents.test.ts
  • 2026-01-24: Completed F073 (notification delivery lifecycle emission):

    • Implemented workflow v2 notification lifecycle events for in-app internal notifications:
      • NOTIFICATION_SENT when an internal notification row is created
      • NOTIFICATION_DELIVERED / NOTIFICATION_FAILED based on Redis Pub/Sub broadcast success/failure
      • NOTIFICATION_READ when a user marks a notification as read
    • Wired emissions into the authoritative internal notification paths:
      • packages/notifications/src/actions/internal-notification-actions/internalNotificationActions.ts
      • packages/notifications/src/realtime/internalNotificationBroadcaster.ts
    • Added shared payload builders + schema-compat unit test coverage:
      • shared/workflow/streams/domainEventBuilders/notificationEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/notificationEventBuilders.test.ts
  • 2026-01-24: Completed F074 (survey/CSAT lifecycle emission):

    • Added shared payload builders + schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/surveyEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/surveyEventBuilders.test.ts
    • Emitted workflow v2 survey lifecycle events from the authoritative survey flows:
      • server/src/services/surveyService.ts now publishes SURVEY_SENT when an invitation is sent, and publishes SURVEY_REMINDER_SENT when a subsequent invitation is sent for the same (ticketId, templateId, contactId) tuple (treated as reminder #N).
      • packages/surveys/src/actions/surveyResponseActions.ts now publishes SURVEY_RESPONSE_RECEIVED when a response is persisted, and publishes CSAT_ALERT_TRIGGERED when the rating is <= NEGATIVE_RATING_THRESHOLD (scope = assigned agent when available, otherwise org).
      • packages/surveys/src/actions/surveyTokenService.ts now publishes SURVEY_EXPIRED (best-effort) when an expired token is resolved (idempotencyKey = survey_expired:<tenant>:<invitationId>).
  • 2026-01-24: Completed F080 (integration sync lifecycle emission):

    • Added workflow v2 integration sync domain payload builders + schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/integrationSyncEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/integrationSyncEventBuilders.test.ts
    • Emitted INTEGRATION_SYNC_STARTED / INTEGRATION_SYNC_COMPLETED / INTEGRATION_SYNC_FAILED from real RMM sync paths (NinjaOne):
      • ee/server/src/lib/integrations/ninjaone/sync/syncEngine.ts publishes lifecycle events for:
        • full sync (runFullSync)
        • incremental sync (runIncrementalSync)
        • single-device sync (syncDevice)
      • Uses SyncOptions.performedBy (when provided) to attribute INTEGRATION_SYNC_STARTED.initiatedByUserId.
  • 2026-01-24: Completed F081 (integration webhook receipt emission):

    • Added safe webhook payload builders:
      • shared/workflow/streams/domainEventBuilders/integrationWebhookEventBuilders.ts
        • sanitizeIntegrationWebhookRawPayload(...) redacts sensitive keys (token/secret/password/apiKey/authorization/signature) and enforces a size cap
        • buildIntegrationWebhookReceivedPayload(...) builds schema-aligned payloads
    • Emitted INTEGRATION_WEBHOOK_RECEIVED from the authoritative NinjaOne webhook handler:
      • ee/server/src/lib/integrations/ninjaone/webhooks/webhookHandler.ts publishes the domain event with:
        • webhookId derived from payload.id/payload.activityId (falls back to sha256-based id)
        • rawPayloadRef set to a generated integration-webhook:<uuid> reference
        • a structured log entry containing a redacted payload snapshot keyed by rawPayloadRef
    • Added schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/__tests__/integrationWebhookEventBuilders.test.ts
  • 2026-01-24: Completed F082 (integration connection health emission):

    • Added workflow v2 integration connection payload builders + schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/integrationConnectionEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/integrationConnectionEventBuilders.test.ts
    • Emitted INTEGRATION_CONNECTED on successful NinjaOne OAuth connect:
      • ee/server/src/app/api/integrations/ninjaone/callback/route.ts
    • Emitted INTEGRATION_DISCONNECTED on manual NinjaOne disconnect:
      • ee/server/src/lib/actions/integrations/ninjaoneActions.ts
    • Note: for RMM, connectionId is set to the tenant integration UUID (integrationId) since the current data model has a single connection row per provider/tenant.
  • 2026-01-24: Completed F083 (integration token lifecycle emission):

    • Added shared payload builders + schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/integrationTokenEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/integrationTokenEventBuilders.test.ts
    • Implemented best-effort emission from the NinjaOne OAuth client:
      • ee/server/src/lib/integrations/ninjaone/ninjaOneClient.ts now publishes INTEGRATION_TOKEN_REFRESH_FAILED when refresh attempts fail (deduped in 5-minute buckets).
      • ee/server/src/lib/integrations/ninjaone/ninjaOneClient.ts now publishes INTEGRATION_TOKEN_EXPIRING when a token expiry timestamp indicates a long-lived token nearing expiration (default window 7 days; suppresses noisy <24h expiries).
    • Propagated integration context into NinjaOne client factories where available:
      • ee/server/src/lib/integrations/ninjaone/sync/* passes integrationId into createNinjaOneClient(...) so token events can include integrationId/connectionId.
  • 2026-01-24: Completed F084 (external mapping change emission):

    • Added shared payload builder:
      • shared/workflow/streams/domainEventBuilders/externalMappingEventBuilders.ts
    • Emitted EXTERNAL_MAPPING_CHANGED from the authoritative accounting mapping CRUD actions:
      • packages/integrations/src/actions/externalMappingActions.ts publishes on create/update/delete for tenant_external_entity_mappings
      • Payload uses provider=integration_type, mappingType=alga_entity_type, and includes previousValue/newValue snapshots (camelCase keys).
    • Added unit coverage for payload shaping:
      • packages/integrations/src/lib/externalMappingWorkflowEvents.ts
      • packages/integrations/src/lib/__tests__/externalMappingWorkflowEvents.test.ts
  • 2026-01-24: Completed F090 (asset lifecycle emission):

    • Added shared payload builders + schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/assetEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/assetEventBuilders.test.ts
    • Emitted workflow v2 asset domain events from authoritative asset and association paths:
      • packages/assets/src/actions/assetActions.ts publishes:
        • ASSET_CREATED on asset creation
        • ASSET_UPDATED on asset updates (with updatedFields + {previous,new} changes derived from the update payload)
        • ASSET_ASSIGNED / ASSET_UNASSIGNED on asset association create/remove (ownerType = ticket/project)
        • ASSET_ASSIGNED on client reassignment when client_id changes (ownerType = client)
        • ASSET_WARRANTY_EXPIRING on 30-day threshold crossings (create/update) via computeAssetWarrantyExpiring(...)
    • Verification:
      • npx vitest run shared/workflow/streams/domainEventBuilders/__tests__/assetEventBuilders.test.ts
      • npx tsc -p packages/assets/tsconfig.json --noEmit
  • 2026-01-24: Completed F091 (media upload + processing emission):

    • Added shared payload builders + schema-compat unit tests:
      • shared/workflow/streams/domainEventBuilders/mediaEventBuilders.ts
      • shared/workflow/streams/domainEventBuilders/__tests__/mediaEventBuilders.test.ts
    • Emitted FILE_UPLOADED from the authoritative file storage services:
      • packages/documents/src/storage/StorageService.ts (alongside existing DOCUMENT_UPLOADED)
      • server/src/lib/storage/StorageService.ts
    • Emitted media processing lifecycle events for document preview generation:
      • MEDIA_PROCESSING_SUCCEEDED / MEDIA_PROCESSING_FAILED from:
        • packages/documents/src/lib/documentPreviewGenerator.ts
        • server/src/lib/utils/documentPreviewGenerator.ts
      • Success outputs include thumbnail/preview file ids when generated; failures include a stable errorCode.
    • Added focused unit coverage:
      • server/src/test/unit/documentPreviewGenerator.test.ts asserts MEDIA_PROCESSING_SUCCEEDED is published for supported file types.
      • packages/documents/tests/storageService.workflowEvents.test.ts asserts FILE_UPLOADED is published on upload.
    • Gotcha: packages/documents/src/storage/StorageService.ts imports @alga-psa/auth/getCurrentUser (which depends on @alga-psa/media); tests that import this module must mock @alga-psa/auth/getCurrentUser to avoid Vite import-analysis failures.

Next Up

  • All features in features.json are now implemented; proceed to tests.json checklist.

  • 2026-01-24: Completed T001 (schema conventions coverage):

    • Added a schema-level regression test that parses event-proposals.md and asserts payload schema conventions:
      • shared/workflow/runtime/__tests__/payloadSchemaConventions.test.ts
      • Ensures tenantId + occurredAt are required, top-level keys are camelCase (no _), and updatedFields/changes are paired.
      • Ensures previous* transition fields have corresponding new* fields, with explicit exceptions for documented unassignment patterns and CONTACT_PRIMARY_SET.
    • Tweaked payload.TicketUnassigned.v1 schema to include optional newAssigneeId/newAssigneeType fields for consistency:
      • shared/workflow/runtime/schemas/ticketEventSchemas.ts
  • 2026-01-24: Completed T002 (schema registry resolves + validates examples):

    • Added an auto-example generator test that:
      • Parses event-proposals.md, derives payload.*.v1 refs, asserts schemaRegistry.has(ref), and validates an auto-generated example payload per ref.
      • shared/workflow/runtime/__tests__/payloadSchemaExamples.test.ts
  • 2026-01-24: Completed T003 (event bus schema coverage):

    • Existing test asserts EVENT_TYPESEventTypeEnum alignment and that every event type has a payload schema mapping:
      • shared/workflow/streams/__tests__/eventBusSchema.expandedEvents.test.ts
  • 2026-01-24: Completed T004 (catalog migration coverage):

    • Added a migration-level coverage test that parses event-proposals.md and asserts the system catalog upsert migration includes every proposed event_type:
      • server/src/test/unit/workflowEventCatalogMigrationCoverage.test.ts
  • 2026-01-24: Completed T005 (simulator form-mode schema + validation coverage):

    • Added a schema-driven test that asserts every proposed payload.*.v1 ref can be converted to JSON schema (form builder input) and that representative schemas reject invalid payloads:
      • shared/workflow/runtime/__tests__/workflowEventFormModeBuilder.test.ts
  • 2026-01-24: Completed T006 (overlap handling coverage):

    • Existing event bus schema regression test asserts overlap decisions are encoded explicitly:
      • shared/workflow/streams/__tests__/eventBusSchema.expandedEvents.test.ts
  • 2026-01-24: Completed T010 (ticket status/priority transition coverage):

    • Existing transition builder unit test asserts TICKET_STATUS_CHANGED + TICKET_PRIORITY_CHANGED include previous*/new* values:
      • packages/tickets/src/lib/__tests__/workflowTicketTransitionEvents.test.ts
  • 2026-01-24: Completed T011 (ticket assignment change coverage):

    • Added schema-level regression coverage to ensure the event bus accepts domain-shaped assignment payloads:
      • shared/workflow/streams/__tests__/ticketAssignmentEvents.test.ts
  • 2026-01-24: Completed T012 (ticket reopened coverage):

    • Existing transition builder unit test asserts TICKET_REOPENED includes previousStatusId/newStatusId:
      • packages/tickets/src/lib/__tests__/workflowTicketTransitionEvents.test.ts
  • 2026-01-24: Completed T013 (ticket escalation/queue change coverage):

    • Existing transition builder unit test asserts TICKET_ESCALATED + TICKET_QUEUE_CHANGED payloads:
      • packages/tickets/src/lib/__tests__/workflowTicketTransitionEvents.test.ts
  • 2026-01-24: Completed T014 (ticket merge/split coverage):

    • Added schema-level regression coverage for relationship event payloads:
      • shared/workflow/streams/__tests__/ticketMergeSplitEvents.test.ts
  • 2026-01-24: Completed T015 (ticket tag change coverage):

    • Existing tag action builder tests cover tag add/remove events (applies to tickets via entityType/entityId):
      • shared/workflow/streams/domainEventBuilders/__tests__/tagEventBuilders.test.ts
  • 2026-01-24: Completed T016 (ticket communication coverage):

    • Existing unit coverage validates TICKET_MESSAGE_ADDED, TICKET_CUSTOMER_REPLIED, and TICKET_INTERNAL_NOTE_ADDED payload construction and schema compatibility:
      • packages/tickets/src/lib/__tests__/workflowTicketCommunicationEvents.test.ts
  • 2026-01-24: Completed T017 (ticket time entry coverage):

    • Existing unit coverage validates TICKET_TIME_ENTRY_ADDED payload building against payload.TicketTimeEntryAdded.v1:
      • server/src/test/unit/timeEntryWorkflowEvents.test.ts
  • 2026-01-24: Completed T018 (ticket SLA stage coverage):

    • Existing unit coverage validates stage enter/met/breached builders and idempotency behavior:
      • packages/tickets/src/lib/__tests__/workflowTicketSlaStageEvents.test.ts
  • 2026-01-24: Completed T019 (ticket approvals are catalog-only):

    • Per PRD decision, TICKET_APPROVAL_* events are catalog-only; coverage relies on:
      • Schema registry example validation (T002): shared/workflow/runtime/__tests__/payloadSchemaExamples.test.ts
      • Migration coverage (T004): server/src/test/unit/workflowEventCatalogMigrationCoverage.test.ts
  • 2026-01-24: Completed T020 (appointment lifecycle coverage):

    • Existing builder unit test validates appointment lifecycle payloads (start/end time + timezone) against schemas:
      • shared/workflow/streams/domainEventBuilders/__tests__/appointmentEventBuilders.test.ts
  • 2026-01-24: Completed T021 (appointment assignment coverage):

    • Existing builder unit test validates APPOINTMENT_ASSIGNED payloads (previous/new assignee) against schemas:
      • shared/workflow/streams/domainEventBuilders/__tests__/appointmentEventBuilders.test.ts
  • 2026-01-24: Completed T022 (schedule block coverage):

    • Existing builder unit test validates SCHEDULE_BLOCK_CREATED and SCHEDULE_BLOCK_DELETED payloads against schemas:
      • shared/workflow/streams/domainEventBuilders/__tests__/scheduleBlockEventBuilders.test.ts

Suggested Phasing (to reduce risk)

Phase 1 (authoritative CRUD/state transitions; low provider dependency):

  • Tickets transitions + assignment
  • Projects task/status
  • Billing invoice lifecycle + payments
  • CRM client/contact changes
  • Tags generic apply/remove

Phase 2 (integration/provider dependent):

  • Email delivery/bounce/complaint/unsubscribe
  • Notifications delivered/read
  • Integrations token lifecycle + webhook receipt

Phase 3 (feature dependent / optional modules):

  • SLA stages, approvals, dispatch/field ops, signatures

Commands / Runbooks

  • Validate plan JSON shape: python3 scripts/validate_plan.py ee/docs/plans/2026-01-23-workflow-event-catalog-domain-events
  • Local verification for this plan (until NX worker issue is resolved):
    • Lint changed files: npx eslint shared/workflow/streams/eventBusSchema.ts shared/workflow/streams/workflowEventPublishHelpers.ts packages/event-bus/src/eventBus.ts packages/event-bus/src/publishers/index.ts server/src/lib/eventBus/index.ts server/src/lib/eventBus/publishers/index.ts
    • Note: npx nx build @alga-psa/shared fails in this sandbox with NX Failed to start plugin worker.

Gotchas

  • (Resolved in this worktree) Earlier sandbox runs had git add / git commit failing with .../.git/worktrees/.../index.lock permission errors. As of 2026-01-23 in /Users/roberisaacs/alga-psa.worktrees/feature/workflow-events-catalog, commits succeed normally.
  • DB-backed vitest integration tests (e.g. server/src/test/integration/ticketBundling.integration.test.ts) require a local Postgres instance matching .env.localtest (observed ECONNREFUSED on localhost:5438 when not running).
  • npm run test:nx currently fails on tools/nx-tests/editionSwapping.test.ts (CE alias ee resolves to packages/ee/src instead of server/src/empty). This appears unrelated to workflow event changes; use targeted vitest runs for verification until fixed.
  • Some older unit tests import deep, non-exported subpaths (e.g. @alga-psa/projects/actions/projectActions) and fail under Vite import-analysis with Missing "./actions/projectActions" specifier...; use source-relative imports in tests or prefer shared-schema-level tests until package exports are updated.
  • npx tsc -p ee/server/tsconfig.json --noEmit currently fails with a pre-existing type error in shared/workflow/runtime/actions/registerEmailWorkflowActions.ts (messageId required vs optional mismatch).

Work Log (continued)

  • 2026-01-24: Completed T023 (capacity thresholds coverage):

    • Added unit coverage that asserts CAPACITY_THRESHOLD_REACHED publishes only on a threshold crossing and does nothing when capacity isnt configured:
      • packages/scheduling/src/lib/__tests__/capacityThresholdWorkflowEvents.publisher.test.ts
    • Refactored maybePublishCapacityThresholdReached(...) to accept optional __deps (publisher + data access functions + clock) so it can be tested without a DB:
      • packages/scheduling/src/lib/capacityThresholdWorkflowEvents.ts
  • 2026-01-24: Completed T024 (dispatch lifecycle “supported only” coverage):

    • Expanded unit coverage to assert dispatch lifecycle gating and technician id normalization:
      • shared/workflow/streams/domainEventBuilders/__tests__/technicianDispatchEventBuilders.test.ts
  • 2026-01-24: Completed T030 (project updated coverage):

    • Coverage already exists via schema-compat builder test asserting updatedFields + {previous,new} changes shape:
      • shared/workflow/streams/domainEventBuilders/__tests__/projectLifecycleEventBuilders.test.ts
  • 2026-01-24: Completed T031 (project status changed coverage):

    • Coverage already exists via schema-compat builder test asserting previousStatus/newStatus transition shape:
      • shared/workflow/streams/domainEventBuilders/__tests__/projectLifecycleEventBuilders.test.ts
  • 2026-01-24: Completed T032 (project task lifecycle coverage):

    • Coverage already exists via schema-compat builder test for task create/assign/status/complete payload shapes:
      • shared/workflow/streams/domainEventBuilders/__tests__/projectTaskEventBuilders.test.ts
  • 2026-01-24: Completed T033 (project dependency coverage):

    • Coverage already exists via schema-compat builder test for dependency blocked/unblocked payload shapes:
      • shared/workflow/streams/domainEventBuilders/__tests__/projectTaskEventBuilders.test.ts
  • 2026-01-24: Completed T034 (project approvals are catalog-only):

    • Per PRD decision, PROJECT_APPROVAL_* events are catalog-only; coverage relies on:
      • Schema registry example validation (T002): shared/workflow/runtime/__tests__/payloadSchemaExamples.test.ts
      • Migration coverage (T004): server/src/test/unit/workflowEventCatalogMigrationCoverage.test.ts
  • 2026-01-24: Completed T040 (invoice send/status/due-date coverage):

    • Coverage exists via billing payload builder unit tests:
      • server/src/test/unit/invoiceWorkflowEvents.test.ts
  • 2026-01-24: Completed T041 (invoice overdue/write-off coverage):

    • Coverage exists via billing payload builder unit tests (days overdue + monetary context):
      • server/src/test/unit/invoiceWorkflowEvents.test.ts
  • 2026-01-24: Completed T042 (payments coverage):

    • Coverage exists via billing payload builder unit tests (recorded/applied/failed/refunded + invoice applications):
      • server/src/test/unit/paymentWorkflowEvents.test.ts
  • 2026-01-24: Completed T043 (credits coverage):

    • Coverage exists via schema-compat builder tests for credit note created/applied/voided payload shapes:
      • shared/workflow/streams/domainEventBuilders/__tests__/creditNoteEventBuilders.test.ts
  • 2026-01-24: Completed T044 (contracts coverage):

    • Coverage exists via schema-compat builder tests for contract lifecycle + renewal-upcoming timing:
      • shared/workflow/streams/domainEventBuilders/__tests__/contractEventBuilders.test.ts
  • 2026-01-24: Completed T045 (recurring billing run coverage):

    • Coverage exists via schema-compat builder tests for run started/completed/failed payload shapes:
      • shared/workflow/streams/domainEventBuilders/__tests__/recurringBillingRunEventBuilders.test.ts
  • 2026-01-24: Completed T050 (CRM client coverage):

    • Coverage exists via schema-compat builder tests for client lifecycle payload shapes (including catalog-only CLIENT_MERGED):
      • shared/workflow/streams/domainEventBuilders/__tests__/clientEventBuilders.test.ts
  • 2026-01-24: Completed T051 (CRM contact coverage):

    • Coverage exists via schema-compat builder tests for contact lifecycle payload shapes (including catalog-only CONTACT_MERGED):
      • shared/workflow/streams/domainEventBuilders/__tests__/contactEventBuilders.test.ts
  • 2026-01-24: Completed T052 (CRM interaction/note coverage):

    • Coverage exists via schema-compat builder tests for interaction logged + note created payload shapes:
      • shared/workflow/streams/domainEventBuilders/__tests__/crmInteractionNoteEventBuilders.test.ts
  • 2026-01-24: Completed T053 (tags coverage):

    • Coverage exists via schema-compat builder tests for definition create/update and tag apply/remove payload shapes:
      • shared/workflow/streams/domainEventBuilders/__tests__/tagEventBuilders.test.ts
  • 2026-01-24: Completed T060 (document upload/delete coverage):

    • Added a unit test that asserts StorageService publishes both DOCUMENT_UPLOADED and DOCUMENT_DELETED with storage metadata:
      • packages/documents/tests/storageService.workflowEvents.test.ts
  • 2026-01-24: Completed T061 (document association/detach coverage):

    • Coverage exists via schema-compat builder tests for document associated/detached payload shapes:
      • shared/workflow/streams/domainEventBuilders/__tests__/documentAssociationEventBuilders.test.ts
  • 2026-01-24: Completed T062 (document generation coverage):

    • Coverage exists via schema-compat builder tests for DOCUMENT_GENERATED payload shape (sourceType/sourceId):
      • shared/workflow/streams/domainEventBuilders/__tests__/documentGeneratedEventBuilders.test.ts
  • 2026-01-24: Completed T063 (document signatures are catalog-only):

    • Per PRD decision, DOCUMENT_SIGNATURE_* events are catalog-only; coverage relies on:
      • Schema registry example validation (T002): shared/workflow/runtime/__tests__/payloadSchemaExamples.test.ts
      • Migration coverage (T004): server/src/test/unit/workflowEventCatalogMigrationCoverage.test.ts
  • 2026-01-24: Completed T070 (inbound email reply coverage):

    • Coverage exists via schema-compat builder tests for INBOUND_EMAIL_REPLY_RECEIVED payload shape:
      • shared/workflow/streams/domainEventBuilders/__tests__/inboundEmailReplyEventBuilders.test.ts
  • 2026-01-24: Completed T071 (outbound email lifecycle coverage):

    • Coverage exists via unit tests for the outbound send pipeline publishing queued/sent/failed events:
      • server/src/test/unit/outboundEmailWorkflowEvents.test.ts
  • 2026-01-24: Completed T072 (email delivery/feedback coverage):

    • Coverage exists via schema-compat builder tests and provider webhook mapping tests:
      • shared/workflow/streams/domainEventBuilders/__tests__/emailFeedbackEventBuilders.test.ts
      • server/src/test/unit/resendWebhookEvents.test.ts
  • 2026-01-24: Completed T073 (notification lifecycle coverage):

    • Coverage exists via schema-compat builder tests for sent/delivered/failed/read payload shapes:
      • shared/workflow/streams/domainEventBuilders/__tests__/notificationEventBuilders.test.ts
  • 2026-01-24: Completed T074 (survey/CSAT lifecycle coverage):

    • Coverage exists via schema-compat builder tests for survey sent/response/reminder/expired and CSAT alert payload shapes:
      • shared/workflow/streams/domainEventBuilders/__tests__/surveyEventBuilders.test.ts
  • 2026-01-24: Completed T080 (integration sync lifecycle coverage):

    • Coverage exists via schema-compat builder tests for sync started/completed/failed payload shapes:
      • shared/workflow/streams/domainEventBuilders/__tests__/integrationSyncEventBuilders.test.ts
  • 2026-01-24: Completed T081 (integration webhook receipt coverage):

    • Coverage exists via schema-compat builder tests for safe redaction and INTEGRATION_WEBHOOK_RECEIVED payload shape:
      • shared/workflow/streams/domainEventBuilders/__tests__/integrationWebhookEventBuilders.test.ts
  • 2026-01-24: Completed T082 (integration connect/disconnect coverage):

    • Coverage exists via schema-compat builder tests for connected/disconnected payload shapes:
      • shared/workflow/streams/domainEventBuilders/__tests__/integrationConnectionEventBuilders.test.ts
  • 2026-01-24: Completed T083 (integration token lifecycle coverage):

    • Coverage exists via schema-compat builder tests for token expiring and refresh-failed payload shapes:
      • shared/workflow/streams/domainEventBuilders/__tests__/integrationTokenEventBuilders.test.ts
  • 2026-01-24: Completed T084 (external mapping changed coverage):

    • Coverage exists via unit tests for mapping payload shaping (previous/new snapshots + mappingType):
      • packages/integrations/src/lib/__tests__/externalMappingWorkflowEvents.test.ts
  • 2026-01-24: Completed T090 (asset lifecycle coverage):

    • Coverage exists via schema-compat builder tests for asset lifecycle + warranty-expiring payload shapes:
      • shared/workflow/streams/domainEventBuilders/__tests__/assetEventBuilders.test.ts
  • 2026-01-24: Completed T091 (media upload + processing coverage):

    • Coverage exists via schema-compat builder tests and preview-generation publish tests:
      • shared/workflow/streams/domainEventBuilders/__tests__/mediaEventBuilders.test.ts
      • packages/documents/tests/storageService.workflowEvents.test.ts (FILE_UPLOADED)
      • server/src/test/unit/documentPreviewGenerator.test.ts (MEDIA_PROCESSING_SUCCEEDED)