Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
64 KiB
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.jsonandee/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+EventPayloadSchemasmapping.
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 validatesevent_typeexists in catalog tables. - There are multiple non-workflow publishers already in the repo (examples found via ripgrep):
packages/billing/src/services/accountingExportService.tspackages/integrations/src/services/calendar/CalendarSyncService.tsserver/src/services/email/EmailProcessor.ts(publishesINBOUND_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)
- Ticket messages vs ticket comments
- Keep existing
TICKET_COMMENT_*as canonical, and map proposedTICKET_MESSAGE_*onto those semantics? - Or introduce
TICKET_MESSAGE_*and deprecateTICKET_COMMENT_*? - Decision (2026-01-23): Introduce
TICKET_MESSAGE_*as canonical workflow v2 domain events; keepTICKET_COMMENT_*as legacy/back-compat (no automatic aliasing due to payload shape differences).
- Keep existing
- 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; keepSCHEDULE_ENTRY_*for lower-level schedule entry changes and existing integrations.
- Reuse existing
- Client/company identifiers
- Standardize payloads on
clientId,companyId, or include both?
- Standardize payloads on
- Payload casing
- Decision (2026-01-23): use camelCase payload keys for all new
payload.*.v1schemas and event bus payloads (documented inee/docs/plans/2026-01-23-workflow-event-catalog-domain-events/PRD.md§6.2).
- Decision (2026-01-23): use camelCase payload keys for all new
- 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 expandingPRD.md§6.2 with:- camelCase key requirement
- required
tenantId+occurredAt - actor field guidance (
actorUserId/actorContactId/ optionalactorType) - 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
tenantIdandoccurredAt - enriches actor fields (
actorType,actorUserId,actorContactId) - carries optional
idempotencyKey
- ensures
- Added publish-time hooks for workflow stream conversion:
shared/workflow/streams/eventBusSchema.tsnow supportsWorkflowPublishHooksandconvertToWorkflowEvent(event, hooks)- workflow
user_idnow preferspayload.actorUserId(falls back topayload.userId)
- Wired hooks through both event bus implementations:
packages/event-bus/src/eventBus.tsandserver/src/lib/eventBus/index.tsaccept{ workflow }publish options
- Added convenience publisher wrappers:
packages/event-bus/src/publishers/index.tsandserver/src/lib/eventBus/publishers/index.tsexportpublishWorkflowEvent(...)
- Added payload enrichment helper
-
2026-01-23: Completed
F003(schema registry payload schemas + registrations):- Added shared payload building blocks in
shared/workflow/runtime/schemas/commonEventPayloadSchemas.ts:tenantId+occurredAtrequired on all new payload schemas- optional actor fields (
actorUserId,actorContactId,actorType) - shared
updatedFields[]andchanges{path:{previous,new}}helpers
- Added
payload.*.v1Zod payload schemas for every event inevent-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
- tickets:
- Centralized schema registrations in
shared/workflow/runtime/schemas/workflowEventPayloadSchemas.tsand updatedshared/workflow/runtime/init.tsto register the full map at init. - Verified registry coverage: all
payload.<PascalCase>.v1refs implied byevent-proposals.mdare present inworkflowEventPayloadSchemas.
- Added shared payload building blocks in
-
2026-01-23: Completed
F004(event bus schema/types + overlap resolution):- Expanded
shared/workflow/streams/eventBusSchema.ts:- added all proposed domain
EventTypeEnumvalues fromevent-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_TYPESentry has a payload schema
- added all proposed domain
- Removed duplicate schema/type definitions by re-exporting shared schema from:
packages/event-bus/src/events.tsserver/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.
- Expanded
-
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)
- 122 proposed events parsed from
- Normalizes
name,description,category, andpayload_schema_reffor each row. - Sets
payload_schema_refaspayload.<PascalCase(event_type)>.v1(e.g.INBOUND_EMAIL_RECEIVED→payload.InboundEmailReceived.v1). - Intentionally does not manage legacy
payload_schemaJSON in catalog rows (workflow v2 uses schema registry viapayload_schema_ref).
- Upserts 134 total events into
- Added idempotent system catalog upsert migration:
-
2026-01-23: Completed
F006(simulation usespayload_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.tsnow:- derives
sourcePayloadSchemaReffrom submission override or catalogpayload_schema_ref - enriches payload via
buildWorkflowPayload(...)(addstenantId,occurredAt,actorUserId,actorType) - rejects invalid payloads with
400including Zodissues, and records an errorworkflow_runtime_event
- derives
- 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.
- Server-side event ingestion now enriches simulator payloads and validates them against the schema registry:
-
2026-01-23: Completed
F010(ticket transition emission):- Emitted workflow v2 domain events from real ticket update paths:
packages/tickets/src/actions/ticketActions.tsnow publishes:TICKET_STATUS_CHANGED,TICKET_PRIORITY_CHANGED,TICKET_UNASSIGNED,TICKET_REOPENED,TICKET_ESCALATED,TICKET_QUEUE_CHANGEDviapublishWorkflowEvent(...)and shared transition detection.packages/tickets/src/actions/optimizedTicketActions.tsupdated similarly (this is the main cached ticket update path).
- Upgraded legacy ticket lifecycle emissions to include workflow v2 context:
TICKET_UPDATED,TICKET_ASSIGNED,TICKET_CLOSEDnow emit viapublishWorkflowEvent(...)(addsoccurredAt+actorUserId), andTICKET_ASSIGNEDincludespreviousAssignee*/newAssignee*fields for domain-style payloads while keeping legacyuserId.
- 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).
- Emitted workflow v2 domain events from real ticket update paths:
-
2026-01-23: Completed
F011(ticket relationship/aggregation emission):- Implemented
TICKET_MERGED/TICKET_SPLITemissions via ticket bundling actions:packages/tickets/src/actions/ticketBundleActions.tsnow publishes:TICKET_MERGEDwhen child tickets are attached to a master bundle (bundle/add children/promote master).TICKET_SPLITwhen child tickets are detached (remove child/unbundle master).
- Semantics note: these events represent bundle attach/detach in today’s product (there is no true destructive “merge tickets” feature yet); payload still follows proposed schema (
sourceTicketId/targetTicketId,originalTicketId/newTicketIds) and includesreasonfor workflow authors.
- Updated bundling integration test harness to mock
@alga-psa/event-bus/publishersto keep DB-backed tests isolated from the event bus.
- Implemented
-
2026-01-23: Completed
F012(ticket communication emission):- Added a shared builder for ticket communication domain events:
packages/tickets/src/lib/workflowTicketCommunicationEvents.tsbuilds additiveTICKET_MESSAGE_ADDEDplus:TICKET_INTERNAL_NOTE_ADDEDfor internal visibilityTICKET_CUSTOMER_REPLIEDwhen 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.tsandpackages/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
- Added a shared builder for ticket communication domain events:
-
2026-01-23: Completed
F013(ticket work tracking emission):- Emitted
TICKET_TIME_ENTRY_ADDEDwhen time entries are created forwork_item_type='ticket':server/src/lib/api/services/TimeEntryService.tspublishes 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.v1viabuildWorkflowPayload(...):server/src/test/unit/timeEntryWorkflowEvents.test.ts
- Emitted
-
2026-01-23: Completed
F014(ticket SLA stage emission):- Implemented ITIL-backed “resolution SLA” stage events when
itil_priority_levelis present:TICKET_SLA_STAGE_ENTEREDemitted on ticket create in:packages/tickets/src/actions/ticketActions.ts(addTicket,createTicketFromAsset)
TICKET_SLA_STAGE_MET/TICKET_SLA_STAGE_BREACHEDemitted 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
resolutiononly (no first-class response/custom stage model in product yet). - Uses
tenantIdas a stableslaPolicyIdplaceholder until a real SLA policy model exists.
- Stage emitted is currently
- Implemented ITIL-backed “resolution SLA” stage events when
-
2026-01-23: Completed
F015(ticket approvals — catalog-only):- Marked
TICKET_APPROVAL_REQUESTED/TICKET_APPROVAL_GRANTED/TICKET_APPROVAL_REJECTEDas 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).
- Marked
-
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.tsemitsAPPOINTMENT_CREATED,APPOINTMENT_RESCHEDULED,APPOINTMENT_CANCELED,APPOINTMENT_COMPLETED,APPOINTMENT_NO_SHOW,APPOINTMENT_ASSIGNEDwhen schedule entries represent appointments (work_item_type='appointment_request'orticket).packages/client-portal/src/actions/client-portal-actions/appointmentRequestActions.tsemitsAPPOINTMENT_CREATED,APPOINTMENT_RESCHEDULED,APPOINTMENT_CANCELED,APPOINTMENT_ASSIGNEDfor client portal appointment requests (actor isCONTACT).packages/scheduling/src/actions/appointmentRequestManagementActions.tsemitsAPPOINTMENT_CREATED/APPOINTMENT_ASSIGNEDfor the legacy “approve request creates schedule entry” fallback; emitsAPPOINTMENT_ASSIGNEDon 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:
timezoneis emitted asUTC(schedule UI and storage treat times as UTC today).APPOINTMENT_NO_SHOWis emitted only when a schedule entry status is set to a no-show variant;partydefaults tocustomeruntil the product captures who no-showed explicitly.
- Emitted workflow v2 appointment domain events from real scheduling/appointment flows:
-
2026-01-23: Completed
F021(schedule block create/delete emission):- Implemented
SCHEDULE_BLOCK_CREATED/SCHEDULE_BLOCK_DELETEDemission for “availability blocks” represented today as private ad-hoc schedule entries:is_private=true,work_item_type='ad_hoc',work_item_id=null, exactly oneassigned_user_idsowner.
- Added shared payload builders:
shared/workflow/streams/domainEventBuilders/scheduleBlockEventBuilders.ts
- Emitted events from scheduling actions:
packages/scheduling/src/actions/scheduleActions.tspublishes 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:
timezoneis emitted asUTC.- 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.
- Implemented
-
2026-01-23: Completed
F022(capacity threshold emission):- Emitted
CAPACITY_THRESHOLD_REACHEDfrom real scheduling mutations:packages/scheduling/src/actions/scheduleActions.tspublishes after create/update/delete viamaybePublishCapacityThresholdReached(...).
- Implemented team/day capacity math (UTC date) based on existing data model:
- Capacity limit = sum of
resources.max_daily_capacityfor 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).
- Capacity limit = sum of
- Added shared payload builder + unit coverage:
shared/workflow/streams/domainEventBuilders/capacityThresholdEventBuilders.tsshared/workflow/streams/domainEventBuilders/__tests__/capacityThresholdEventBuilders.test.ts
- Added scheduling unit coverage for threshold/date math:
packages/scheduling/src/lib/__tests__/capacityThresholdWorkflowEvents.test.ts
- Emitted
-
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.tsnow publishes:TECHNICIAN_DISPATCHEDwhen technicians are newly assigned to a ticket/appointment schedule entryTECHNICIAN_EN_ROUTE/TECHNICIAN_ARRIVEDwhen schedule entrystatustransitions to an en-route / arrived value (case-insensitive, supports common variants)TECHNICIAN_CHECKED_OUTwhen schedule entrystatustransitions 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
- Added shared payload/status helpers:
-
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.tspublishesPROJECT_UPDATED(withupdatedFields+{previous,new}changes) andPROJECT_STATUS_CHANGEDon status transitions viapublishWorkflowEvent(...).server/src/lib/api/services/ProjectService.tspublishes the same domain events for REST API updates.
- Kept legacy email notifications working with the new payload shape:
server/src/lib/eventBus/subscribers/projectEmailSubscriber.tsnow supports both legacy{ changes: Record<string, unknown> }and domain{ changes: Record<string, {previous,new}> }shapes and usesactorUserIdwhenuserIdis 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.
- Added shared payload builders:
-
2026-01-23: Completed
F031(project task lifecycle emission):- Added shared payload builders + schema-compat unit tests:
shared/workflow/streams/domainEventBuilders/projectTaskEventBuilders.tsshared/workflow/streams/domainEventBuilders/__tests__/projectTaskEventBuilders.test.ts
- Emitted workflow v2 domain events from real task creation/update paths:
packages/projects/src/actions/projectTaskActions.tspublishes:PROJECT_TASK_CREATEDon task create/duplicatePROJECT_TASK_ASSIGNEDon primary assignee changes (domain payload withassignedToId/assignedToType)PROJECT_TASK_STATUS_CHANGED+PROJECT_TASK_COMPLETEDwhen status mapping transitions (completed = enters a closed status)
packages/projects/src/actions/phaseTaskImportActions.tspublishesPROJECT_TASK_CREATED(+PROJECT_TASK_ASSIGNEDwhen assigned) for imported tasks.server/src/lib/api/services/ProjectService.tspublishes the same task lifecycle events for REST API create/update paths.server/src/lib/models/scheduleEntry.tsnow publishes domain-shapedPROJECT_TASK_ASSIGNEDwhen 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
- Added shared payload builders + schema-compat unit tests:
-
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.tspublishes:PROJECT_TASK_DEPENDENCY_BLOCKEDwhen a blocking relationship is created (dependency_typein {blocks,blocked_by})PROJECT_TASK_DEPENDENCY_UNBLOCKEDwhen a blocking relationship is removed
- Notes/constraints:
- Only blocking relationships emit events;
related_todependencies remain non-workflow.
- Only blocking relationships emit events;
- Added shared payload builders + schema-compat unit tests:
-
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 (mirrorsTICKET_APPROVAL_*rationale) until a real project approval feature exists. - Updated
ee/docs/plans/2026-01-23-workflow-event-catalog-domain-events/PRD.mdOpen Questions #4 to reflectPROJECT_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.tsnow publishes:INVOICE_SENTonsendInvoice(delivery method inferred; includesclientId,sentByUserId,sentAt)INVOICE_STATUS_CHANGEDon all invoice status transitions acrossupdate,finalizeInvoice,sendInvoice,recordPayment,applyCredit,recordRefund,bulkUpdateStatus, and soft-cancel indeleteINVOICE_DUE_DATE_CHANGEDfromupdatewhen due date changesINVOICE_OVERDUEwhen status transitions intooverdue(amount due computed from payments + credits)INVOICE_WRITTEN_OFFwhen transitioningoverdue→cancelledwith a remaining balance (maps “write off” onto today’s available status model)
- Added schema-compat unit coverage:
server/src/test/unit/invoiceWorkflowEvents.test.ts
- Added billing workflow payload builders:
-
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.tsnow publish:PAYMENT_RECORDED+PAYMENT_APPLIEDonrecordPaymentPAYMENT_REFUNDEDonrecordRefund
- Stripe webhook processing in
ee/server/src/lib/payments/PaymentService.tsnow publishes:PAYMENT_RECORDED+PAYMENT_APPLIEDon successful payment recordingPAYMENT_REFUNDEDon refund recordingPAYMENT_FAILEDonpayment_intent.payment_failedevents (when amount is present)
- Manual invoice payments/refunds in
- Added schema-compat unit coverage:
server/src/test/unit/paymentWorkflowEvents.test.ts
- Verification:
npx vitest run server/src/test/unit/paymentWorkflowEvents.test.ts
- Added billing workflow payload builders:
-
2026-01-23: Completed
F042(credit events emission):- Added shared billing payload builders + schema-compat tests:
shared/workflow/streams/domainEventBuilders/creditNoteEventBuilders.tsshared/workflow/streams/domainEventBuilders/__tests__/creditNoteEventBuilders.test.ts
- Emitted workflow v2 credit note domain events from real credit flows:
packages/billing/src/actions/creditActions.tsnow publishes:CREDIT_NOTE_CREATEDwhen prepayment credit tracking entries are createdCREDIT_NOTE_APPLIEDper credit source used when applying credits to an invoice (supports multi-credit allocation)
packages/billing/src/actions/invoiceModification.tsnow publishes:CREDIT_NOTE_CREATEDwhen finalizing a negative invoice that issues credit (credit_issuance_from_negative_invoice)CREDIT_NOTE_VOIDEDwhen hard-deleting a negative invoice that issued unused credit (maps delete → void)
- Verification:
npx vitest run shared/workflow/streams/domainEventBuilders/__tests__/creditNoteEventBuilders.test.ts
- Added shared billing payload builders + schema-compat tests:
-
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.tspublishes:CONTRACT_CREATEDonassignContractToClientandcreateClientContractCONTRACT_UPDATED+CONTRACT_STATUS_CHANGED+CONTRACT_RENEWAL_UPCOMING(on threshold crossing) onupdateClientContractCONTRACT_STATUS_CHANGEDondeactivateClientContract
packages/billing/src/actions/contractWizardActions.tspublishes:CONTRACT_CREATED(+ optionalCONTRACT_RENEWAL_UPCOMING) oncreateClientContractFromWizard
- Added schema-compat unit test coverage:
shared/workflow/streams/domainEventBuilders/__tests__/contractEventBuilders.test.ts
- Added shared contract payload builders + renewal timing helper:
-
2026-01-23: Completed
F044(recurring billing run events emission):- Added shared payload builders + schema-compat unit tests:
shared/workflow/streams/domainEventBuilders/recurringBillingRunEventBuilders.tsshared/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.tspublishesRECURRING_BILLING_RUN_STARTED/COMPLETED(withinvoicesCreated,failedCount) andFAILEDfor 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 --noEmitcurrently fails due to existing TS errors inpackages/billing/src/actions/contractWizardActions.tsandpackages/billing/src/actions/creditActions.ts(pre-existing; not addressed in this item).
- Added shared payload builders + schema-compat unit tests:
-
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.tsnow publishes:CLIENT_CREATEDon createCLIENT_UPDATEDon update (withupdatedFields+{previous,new}changes)CLIENT_STATUS_CHANGEDwhenproperties.statustransitionsCLIENT_OWNER_ASSIGNEDwhenaccount_manager_idchanges to a userCLIENT_ARCHIVEDwhenis_inactivetransitions to true (includingmarkClientInactiveWithContacts)
server/src/lib/api/services/ClientService.tsnow 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_MERGEDremains catalog-only for now (no authoritative client merge/consolidate path exists to emit from today).
- Added shared payload builders:
-
2026-01-23: Completed
F051(CRM contact events emission):- Added shared payload builders + schema-compat unit tests:
shared/workflow/streams/domainEventBuilders/contactEventBuilders.tsshared/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.tsxpublishesCONTACT_CREATED,CONTACT_UPDATED,CONTACT_ARCHIVED(whenis_inactivetransitionsfalse → true) viapublishWorkflowEvent(...).server/src/lib/api/services/ContactService.tspublishes the same domain events for REST API create/update paths.
- Implemented
CONTACT_PRIMARY_SETemission from the authoritative client billing contact change:packages/clients/src/actions/clientActions.tspublishes whenbilling_contact_idchanges to a non-empty contact id.server/src/lib/api/services/ClientService.tspublishes the same event for REST API updates.
- Notes/constraints:
CONTACT_MERGEDremains catalog-only for now (no authoritative “merge contacts” path exists in product code today).
- Added shared payload builders + schema-compat unit tests:
-
2026-01-23: Completed
F052(CRM interaction/note events emission):- Added shared payload builders + schema-compat unit tests:
shared/workflow/streams/domainEventBuilders/crmInteractionNoteEventBuilders.tsshared/workflow/streams/domainEventBuilders/__tests__/crmInteractionNoteEventBuilders.test.ts
- Emitted workflow v2 domain events from real CRM activity paths:
packages/clients/src/actions/interactionActions.tspublishesINTERACTION_LOGGEDon interaction creation (deriveschannelfrom interaction type; ensures interactions are linked to aclientId).packages/clients/src/actions/clientNoteActions.tspublishesNOTE_CREATEDwhen a notes document is first created/linked to a client.packages/clients/src/actions/contact-actions/contactNoteActions.tspublishesNOTE_CREATEDwhen a notes document is first created/linked to a contact.
- Added shared payload builders + schema-compat unit tests:
-
2026-01-23: Completed
F053(tags events emission):- Added shared payload builders + schema-compat unit tests:
shared/workflow/streams/domainEventBuilders/tagEventBuilders.tsshared/workflow/streams/domainEventBuilders/__tests__/tagEventBuilders.test.ts
- Emitted workflow v2 tag domain events from tag actions:
packages/tags/src/actions/tagActions.tspublishesTAG_DEFINITION_CREATED,TAG_DEFINITION_UPDATED,TAG_APPLIED,TAG_REMOVED(uses definition id astagId, andentityType/entityIdfromtagged_type/tagged_id).
- Added
getOrCreateWithStatusto detect definition creates reliably:packages/tags/src/models/tagDefinition.ts
- Added shared payload builders + schema-compat unit tests:
-
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.tspublishes:DOCUMENT_UPLOADEDafter file record creation (includesdocumentId,fileName,contentType,sizeBytes,storageKey)DOCUMENT_DELETEDafter storage delete + soft delete (includesdocumentId,deletedAt)
- Verification:
npx tsc -p packages/documents/tsconfig.json --noEmitnpx vitest run packages/documents/tests/storageConfig.test.ts
- Notes/constraints:
documentIdmaps 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).
- Added shared payload builders:
-
2026-01-24: Completed
F061(document association/detach emission):- Added shared payload builders + schema-compat unit tests:
shared/workflow/streams/domainEventBuilders/documentAssociationEventBuilders.tsshared/workflow/streams/domainEventBuilders/__tests__/documentAssociationEventBuilders.test.ts
- Emitted workflow v2 document association events from real document flows:
packages/documents/src/actions/documentActions.tspublishes:DOCUMENT_ASSOCIATEDon association creation (bulk associate and upload-time associations; also client/contract-specific helpers)DOCUMENT_DETACHEDon manual detach and on document deletion (reason=document_deleted)
packages/documents/src/actions/documentBlockContentActions.tspublishesDOCUMENT_ASSOCIATEDwhen a block-document is created and linked to an entity
- Verification:
npx vitest run shared/workflow/streams/domainEventBuilders/__tests__/documentAssociationEventBuilders.test.ts
- Added shared payload builders + schema-compat unit tests:
-
2026-01-24: Completed
F062(document generation emission):- Added shared payload builder + schema-compat unit test:
shared/workflow/streams/domainEventBuilders/documentGeneratedEventBuilders.tsshared/workflow/streams/domainEventBuilders/__tests__/documentGeneratedEventBuilders.test.ts
- Emitted
DOCUMENT_GENERATEDfrom the authoritative PDF generation path (covers invoice PDFs and document-to-PDF conversion):server/src/services/pdf-generation.service.tspublishesDOCUMENT_GENERATEDafter storing the generated PDF inexternal_filessourceTypeisinvoicewhen generating an invoice PDF, otherwisedocumentsourceIdis the invoice id / source document id
- Notes/constraints:
- Publishing is best-effort (errors are logged but do not fail PDF generation/storage).
- Added shared payload builder + schema-compat unit test:
-
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, andDOCUMENT_SIGNATURE_EXPIREDas catalog-only until an e-sign feature is implemented. - Updated
ee/docs/plans/2026-01-23-workflow-event-catalog-domain-events/PRD.mdOpen Questions #4 to record the decision.
-
2026-01-24: Completed
F070(inbound email reply emission):- Emitted
INBOUND_EMAIL_REPLY_RECEIVEDwhen 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 optionalinboundReplyEventinput. - Updated both workflow definitions to pass
matchedBy(reply_tokenvsthread_headers) and normalizedreceivedAtfor 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.tsshared/workflow/streams/domainEventBuilders/__tests__/inboundEmailReplyEventBuilders.test.ts
- Emitted
-
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.tsnow publishes best-effort:OUTBOUND_EMAIL_QUEUEDimmediately before provider send attemptOUTBOUND_EMAIL_SENTorOUTBOUND_EMAIL_FAILEDafter 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).
- Uses a generated workflow
- Added unit coverage:
server/src/test/unit/outboundEmailWorkflowEvents.test.ts
- Emitted workflow v2 outbound lifecycle events from the authoritative outbound send abstraction:
-
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.tsserver/src/services/email/webhooks/resendWebhookEvents.ts(signature verify + mapping)
- Added correlation tags/headers so provider callbacks can map back to
tenantId+ workflowmessageId:server/src/lib/email/BaseEmailService.tsnow setsX-Alga-Workflow-Message-Id/X-Alga-Tenant-Idheaders andalga_*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.tsshared/workflow/streams/domainEventBuilders/__tests__/emailFeedbackEventBuilders.test.tsserver/src/test/unit/resendWebhookEvents.test.ts
- Implemented Resend delivery/feedback webhook ingestion (Svix signature verification):
-
2026-01-24: Completed
F073(notification delivery lifecycle emission):- Implemented workflow v2 notification lifecycle events for in-app internal notifications:
NOTIFICATION_SENTwhen an internal notification row is createdNOTIFICATION_DELIVERED/NOTIFICATION_FAILEDbased on Redis Pub/Sub broadcast success/failureNOTIFICATION_READwhen a user marks a notification as read
- Wired emissions into the authoritative internal notification paths:
packages/notifications/src/actions/internal-notification-actions/internalNotificationActions.tspackages/notifications/src/realtime/internalNotificationBroadcaster.ts
- Added shared payload builders + schema-compat unit test coverage:
shared/workflow/streams/domainEventBuilders/notificationEventBuilders.tsshared/workflow/streams/domainEventBuilders/__tests__/notificationEventBuilders.test.ts
- Implemented workflow v2 notification lifecycle events for in-app internal notifications:
-
2026-01-24: Completed
F074(survey/CSAT lifecycle emission):- Added shared payload builders + schema-compat unit tests:
shared/workflow/streams/domainEventBuilders/surveyEventBuilders.tsshared/workflow/streams/domainEventBuilders/__tests__/surveyEventBuilders.test.ts
- Emitted workflow v2 survey lifecycle events from the authoritative survey flows:
server/src/services/surveyService.tsnow publishesSURVEY_SENTwhen an invitation is sent, and publishesSURVEY_REMINDER_SENTwhen a subsequent invitation is sent for the same(ticketId, templateId, contactId)tuple (treated as reminder #N).packages/surveys/src/actions/surveyResponseActions.tsnow publishesSURVEY_RESPONSE_RECEIVEDwhen a response is persisted, and publishesCSAT_ALERT_TRIGGEREDwhen the rating is <=NEGATIVE_RATING_THRESHOLD(scope = assigned agent when available, otherwise org).packages/surveys/src/actions/surveyTokenService.tsnow publishesSURVEY_EXPIRED(best-effort) when an expired token is resolved (idempotencyKey =survey_expired:<tenant>:<invitationId>).
- Added shared payload builders + schema-compat unit tests:
-
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.tsshared/workflow/streams/domainEventBuilders/__tests__/integrationSyncEventBuilders.test.ts
- Emitted
INTEGRATION_SYNC_STARTED/INTEGRATION_SYNC_COMPLETED/INTEGRATION_SYNC_FAILEDfrom real RMM sync paths (NinjaOne):ee/server/src/lib/integrations/ninjaone/sync/syncEngine.tspublishes lifecycle events for:- full sync (
runFullSync) - incremental sync (
runIncrementalSync) - single-device sync (
syncDevice)
- full sync (
- Uses
SyncOptions.performedBy(when provided) to attributeINTEGRATION_SYNC_STARTED.initiatedByUserId.
- Added workflow v2 integration sync domain payload builders + schema-compat unit tests:
-
2026-01-24: Completed
F081(integration webhook receipt emission):- Added safe webhook payload builders:
shared/workflow/streams/domainEventBuilders/integrationWebhookEventBuilders.tssanitizeIntegrationWebhookRawPayload(...)redacts sensitive keys (token/secret/password/apiKey/authorization/signature) and enforces a size capbuildIntegrationWebhookReceivedPayload(...)builds schema-aligned payloads
- Emitted
INTEGRATION_WEBHOOK_RECEIVEDfrom the authoritative NinjaOne webhook handler:ee/server/src/lib/integrations/ninjaone/webhooks/webhookHandler.tspublishes the domain event with:webhookIdderived frompayload.id/payload.activityId(falls back to sha256-based id)rawPayloadRefset to a generatedintegration-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
- Added safe webhook payload builders:
-
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.tsshared/workflow/streams/domainEventBuilders/__tests__/integrationConnectionEventBuilders.test.ts
- Emitted
INTEGRATION_CONNECTEDon successful NinjaOne OAuth connect:ee/server/src/app/api/integrations/ninjaone/callback/route.ts
- Emitted
INTEGRATION_DISCONNECTEDon manual NinjaOne disconnect:ee/server/src/lib/actions/integrations/ninjaoneActions.ts
- Note: for RMM,
connectionIdis set to the tenant integration UUID (integrationId) since the current data model has a single connection row per provider/tenant.
- Added workflow v2 integration connection payload builders + schema-compat unit tests:
-
2026-01-24: Completed
F083(integration token lifecycle emission):- Added shared payload builders + schema-compat unit tests:
shared/workflow/streams/domainEventBuilders/integrationTokenEventBuilders.tsshared/workflow/streams/domainEventBuilders/__tests__/integrationTokenEventBuilders.test.ts
- Implemented best-effort emission from the NinjaOne OAuth client:
ee/server/src/lib/integrations/ninjaone/ninjaOneClient.tsnow publishesINTEGRATION_TOKEN_REFRESH_FAILEDwhen refresh attempts fail (deduped in 5-minute buckets).ee/server/src/lib/integrations/ninjaone/ninjaOneClient.tsnow publishesINTEGRATION_TOKEN_EXPIRINGwhen 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/*passesintegrationIdintocreateNinjaOneClient(...)so token events can includeintegrationId/connectionId.
- Added shared payload builders + schema-compat unit tests:
-
2026-01-24: Completed
F084(external mapping change emission):- Added shared payload builder:
shared/workflow/streams/domainEventBuilders/externalMappingEventBuilders.ts
- Emitted
EXTERNAL_MAPPING_CHANGEDfrom the authoritative accounting mapping CRUD actions:packages/integrations/src/actions/externalMappingActions.tspublishes on create/update/delete fortenant_external_entity_mappings- Payload uses
provider=integration_type,mappingType=alga_entity_type, and includespreviousValue/newValuesnapshots (camelCase keys).
- Added unit coverage for payload shaping:
packages/integrations/src/lib/externalMappingWorkflowEvents.tspackages/integrations/src/lib/__tests__/externalMappingWorkflowEvents.test.ts
- Added shared payload builder:
-
2026-01-24: Completed
F090(asset lifecycle emission):- Added shared payload builders + schema-compat unit tests:
shared/workflow/streams/domainEventBuilders/assetEventBuilders.tsshared/workflow/streams/domainEventBuilders/__tests__/assetEventBuilders.test.ts
- Emitted workflow v2 asset domain events from authoritative asset and association paths:
packages/assets/src/actions/assetActions.tspublishes:ASSET_CREATEDon asset creationASSET_UPDATEDon asset updates (withupdatedFields+{previous,new}changesderived from the update payload)ASSET_ASSIGNED/ASSET_UNASSIGNEDon asset association create/remove (ownerType =ticket/project)ASSET_ASSIGNEDon client reassignment whenclient_idchanges (ownerType =client)ASSET_WARRANTY_EXPIRINGon 30-day threshold crossings (create/update) viacomputeAssetWarrantyExpiring(...)
- Verification:
npx vitest run shared/workflow/streams/domainEventBuilders/__tests__/assetEventBuilders.test.tsnpx tsc -p packages/assets/tsconfig.json --noEmit
- Added shared payload builders + schema-compat unit tests:
-
2026-01-24: Completed
F091(media upload + processing emission):- Added shared payload builders + schema-compat unit tests:
shared/workflow/streams/domainEventBuilders/mediaEventBuilders.tsshared/workflow/streams/domainEventBuilders/__tests__/mediaEventBuilders.test.ts
- Emitted
FILE_UPLOADEDfrom the authoritative file storage services:packages/documents/src/storage/StorageService.ts(alongside existingDOCUMENT_UPLOADED)server/src/lib/storage/StorageService.ts
- Emitted media processing lifecycle events for document preview generation:
MEDIA_PROCESSING_SUCCEEDED/MEDIA_PROCESSING_FAILEDfrom:packages/documents/src/lib/documentPreviewGenerator.tsserver/src/lib/utils/documentPreviewGenerator.ts
- Success outputs include
thumbnail/previewfile ids when generated; failures include a stableerrorCode.
- Added focused unit coverage:
server/src/test/unit/documentPreviewGenerator.test.tsassertsMEDIA_PROCESSING_SUCCEEDEDis published for supported file types.packages/documents/tests/storageService.workflowEvents.test.tsassertsFILE_UPLOADEDis published on upload.
- Gotcha:
packages/documents/src/storage/StorageService.tsimports@alga-psa/auth/getCurrentUser(which depends on@alga-psa/media); tests that import this module must mock@alga-psa/auth/getCurrentUserto avoid Vite import-analysis failures.
- Added shared payload builders + schema-compat unit tests:
Next Up
-
All features in
features.jsonare now implemented; proceed totests.jsonchecklist. -
2026-01-24: Completed
T001(schema conventions coverage):- Added a schema-level regression test that parses
event-proposals.mdand asserts payload schema conventions:shared/workflow/runtime/__tests__/payloadSchemaConventions.test.ts- Ensures
tenantId+occurredAtare required, top-level keys are camelCase (no_), andupdatedFields/changesare paired. - Ensures
previous*transition fields have correspondingnew*fields, with explicit exceptions for documented unassignment patterns andCONTACT_PRIMARY_SET.
- Tweaked
payload.TicketUnassigned.v1schema to include optionalnewAssigneeId/newAssigneeTypefields for consistency:shared/workflow/runtime/schemas/ticketEventSchemas.ts
- Added a schema-level regression test that parses
-
2026-01-24: Completed
T002(schema registry resolves + validates examples):- Added an auto-example generator test that:
- Parses
event-proposals.md, derivespayload.*.v1refs, assertsschemaRegistry.has(ref), and validates an auto-generated example payload per ref. shared/workflow/runtime/__tests__/payloadSchemaExamples.test.ts
- Parses
- Added an auto-example generator test that:
-
2026-01-24: Completed
T003(event bus schema coverage):- Existing test asserts
EVENT_TYPES↔EventTypeEnumalignment and that every event type has a payload schema mapping:shared/workflow/streams/__tests__/eventBusSchema.expandedEvents.test.ts
- Existing test asserts
-
2026-01-24: Completed
T004(catalog migration coverage):- Added a migration-level coverage test that parses
event-proposals.mdand asserts the system catalog upsert migration includes every proposedevent_type:server/src/test/unit/workflowEventCatalogMigrationCoverage.test.ts
- Added a migration-level coverage test that parses
-
2026-01-24: Completed
T005(simulator form-mode schema + validation coverage):- Added a schema-driven test that asserts every proposed
payload.*.v1ref can be converted to JSON schema (form builder input) and that representative schemas reject invalid payloads:shared/workflow/runtime/__tests__/workflowEventFormModeBuilder.test.ts
- Added a schema-driven test that asserts every proposed
-
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
- Existing event bus schema regression test asserts overlap decisions are encoded explicitly:
-
2026-01-24: Completed
T010(ticket status/priority transition coverage):- Existing transition builder unit test asserts
TICKET_STATUS_CHANGED+TICKET_PRIORITY_CHANGEDincludeprevious*/new*values:packages/tickets/src/lib/__tests__/workflowTicketTransitionEvents.test.ts
- Existing transition builder unit test asserts
-
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
- Added schema-level regression coverage to ensure the event bus accepts domain-shaped assignment payloads:
-
2026-01-24: Completed
T012(ticket reopened coverage):- Existing transition builder unit test asserts
TICKET_REOPENEDincludespreviousStatusId/newStatusId:packages/tickets/src/lib/__tests__/workflowTicketTransitionEvents.test.ts
- Existing transition builder unit test asserts
-
2026-01-24: Completed
T013(ticket escalation/queue change coverage):- Existing transition builder unit test asserts
TICKET_ESCALATED+TICKET_QUEUE_CHANGEDpayloads:packages/tickets/src/lib/__tests__/workflowTicketTransitionEvents.test.ts
- Existing transition builder unit test asserts
-
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
- Added schema-level regression coverage for relationship event payloads:
-
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
- Existing tag action builder tests cover tag add/remove events (applies to tickets via
-
2026-01-24: Completed
T016(ticket communication coverage):- Existing unit coverage validates
TICKET_MESSAGE_ADDED,TICKET_CUSTOMER_REPLIED, andTICKET_INTERNAL_NOTE_ADDEDpayload construction and schema compatibility:packages/tickets/src/lib/__tests__/workflowTicketCommunicationEvents.test.ts
- Existing unit coverage validates
-
2026-01-24: Completed
T017(ticket time entry coverage):- Existing unit coverage validates
TICKET_TIME_ENTRY_ADDEDpayload building againstpayload.TicketTimeEntryAdded.v1:server/src/test/unit/timeEntryWorkflowEvents.test.ts
- Existing unit coverage validates
-
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
- Existing unit coverage validates stage enter/met/breached builders and idempotency behavior:
-
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
- Schema registry example validation (
- Per PRD decision,
-
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
- Existing builder unit test validates appointment lifecycle payloads (start/end time + timezone) against schemas:
-
2026-01-24: Completed
T021(appointment assignment coverage):- Existing builder unit test validates
APPOINTMENT_ASSIGNEDpayloads (previous/new assignee) against schemas:shared/workflow/streams/domainEventBuilders/__tests__/appointmentEventBuilders.test.ts
- Existing builder unit test validates
-
2026-01-24: Completed
T022(schedule block coverage):- Existing builder unit test validates
SCHEDULE_BLOCK_CREATEDandSCHEDULE_BLOCK_DELETEDpayloads against schemas:shared/workflow/streams/domainEventBuilders/__tests__/scheduleBlockEventBuilders.test.ts
- Existing builder unit test validates
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/sharedfails in this sandbox withNX Failed to start plugin worker.
- Lint changed files:
Gotchas
- (Resolved in this worktree) Earlier sandbox runs had
git add/git commitfailing with.../.git/worktrees/.../index.lockpermission 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(observedECONNREFUSEDonlocalhost:5438when not running). npm run test:nxcurrently fails ontools/nx-tests/editionSwapping.test.ts(CE aliaseeresolves topackages/ee/srcinstead ofserver/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 withMissing "./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 --noEmitcurrently fails with a pre-existing type error inshared/workflow/runtime/actions/registerEmailWorkflowActions.ts(messageIdrequired vs optional mismatch).
Work Log (continued)
-
2026-01-24: Completed
T023(capacity thresholds coverage):- Added unit coverage that asserts
CAPACITY_THRESHOLD_REACHEDpublishes only on a threshold crossing and does nothing when capacity isn’t 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
- Added unit coverage that asserts
-
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
- Expanded unit coverage to assert dispatch lifecycle gating and technician id normalization:
-
2026-01-24: Completed
T030(project updated coverage):- Coverage already exists via schema-compat builder test asserting
updatedFields+{previous,new}changesshape:shared/workflow/streams/domainEventBuilders/__tests__/projectLifecycleEventBuilders.test.ts
- Coverage already exists via schema-compat builder test asserting
-
2026-01-24: Completed
T031(project status changed coverage):- Coverage already exists via schema-compat builder test asserting
previousStatus/newStatustransition shape:shared/workflow/streams/domainEventBuilders/__tests__/projectLifecycleEventBuilders.test.ts
- Coverage already exists via schema-compat builder test asserting
-
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
- Coverage already exists via schema-compat builder test for task create/assign/status/complete payload shapes:
-
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
- Coverage already exists via schema-compat builder test for dependency blocked/unblocked payload shapes:
-
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
- Schema registry example validation (
- Per PRD decision,
-
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
- Coverage exists via billing payload builder unit tests:
-
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
- Coverage exists via billing payload builder unit tests (days overdue + monetary context):
-
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
- Coverage exists via billing payload builder unit tests (recorded/applied/failed/refunded + invoice applications):
-
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
- Coverage exists via schema-compat builder tests for credit note created/applied/voided payload shapes:
-
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
- Coverage exists via schema-compat builder tests for contract lifecycle + renewal-upcoming timing:
-
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
- Coverage exists via schema-compat builder tests for run started/completed/failed payload shapes:
-
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
- Coverage exists via schema-compat builder tests for client lifecycle payload shapes (including catalog-only
-
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
- Coverage exists via schema-compat builder tests for contact lifecycle payload shapes (including catalog-only
-
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
- Coverage exists via schema-compat builder tests for interaction logged + note created payload shapes:
-
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
- Coverage exists via schema-compat builder tests for definition create/update and tag apply/remove payload shapes:
-
2026-01-24: Completed
T060(document upload/delete coverage):- Added a unit test that asserts
StorageServicepublishes bothDOCUMENT_UPLOADEDandDOCUMENT_DELETEDwith storage metadata:packages/documents/tests/storageService.workflowEvents.test.ts
- Added a unit test that asserts
-
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
- Coverage exists via schema-compat builder tests for document associated/detached payload shapes:
-
2026-01-24: Completed
T062(document generation coverage):- Coverage exists via schema-compat builder tests for
DOCUMENT_GENERATEDpayload shape (sourceType/sourceId):shared/workflow/streams/domainEventBuilders/__tests__/documentGeneratedEventBuilders.test.ts
- Coverage exists via schema-compat builder tests for
-
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
- Schema registry example validation (
- Per PRD decision,
-
2026-01-24: Completed
T070(inbound email reply coverage):- Coverage exists via schema-compat builder tests for
INBOUND_EMAIL_REPLY_RECEIVEDpayload shape:shared/workflow/streams/domainEventBuilders/__tests__/inboundEmailReplyEventBuilders.test.ts
- Coverage exists via schema-compat builder tests for
-
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
- Coverage exists via unit tests for the outbound send pipeline publishing queued/sent/failed events:
-
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.tsserver/src/test/unit/resendWebhookEvents.test.ts
- Coverage exists via schema-compat builder tests and provider webhook mapping tests:
-
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
- Coverage exists via schema-compat builder tests for sent/delivered/failed/read payload shapes:
-
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
- Coverage exists via schema-compat builder tests for survey sent/response/reminder/expired and CSAT alert payload shapes:
-
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
- Coverage exists via schema-compat builder tests for sync started/completed/failed payload shapes:
-
2026-01-24: Completed
T081(integration webhook receipt coverage):- Coverage exists via schema-compat builder tests for safe redaction and
INTEGRATION_WEBHOOK_RECEIVEDpayload shape:shared/workflow/streams/domainEventBuilders/__tests__/integrationWebhookEventBuilders.test.ts
- Coverage exists via schema-compat builder tests for safe redaction and
-
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
- Coverage exists via schema-compat builder tests for connected/disconnected payload shapes:
-
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
- Coverage exists via schema-compat builder tests for token expiring and refresh-failed payload shapes:
-
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
- Coverage exists via unit tests for mapping payload shaping (previous/new snapshots + mappingType):
-
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
- Coverage exists via schema-compat builder tests for asset lifecycle + warranty-expiring payload shapes:
-
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.tspackages/documents/tests/storageService.workflowEvents.test.ts(FILE_UPLOADED)server/src/test/unit/documentPreviewGenerator.test.ts(MEDIA_PROCESSING_SUCCEEDED)
- Coverage exists via schema-compat builder tests and preview-generation publish tests: