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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
186 lines
13 KiB
Markdown
186 lines
13 KiB
Markdown
# SCRATCHPAD — Outbound Webhooks for Projects
|
|
|
|
## Decisions (with rationale)
|
|
|
|
- **2026-05-15 — Single `project` allowlist entity** (user-confirmed via
|
|
AskUserQuestion). Both project- and task-level events route to `project`;
|
|
`webhookEntityForEventType()` unchanged. Accepted cost: field picker for
|
|
`project.created` lists task-only fields. Mitigation: task events pass
|
|
`extraAlwaysIncluded:['task_id']` to the projection helper so `task_id`
|
|
survives even when not selected.
|
|
- **2026-05-15 — Rename `projectWebhookPayload` → `applyPayloadAllowlist`.**
|
|
The verb "project" (to project/filter) collides with the new `project`
|
|
entity noun; `projectWebhookPayload('project', …)` is unreadable. Cheap now —
|
|
Phase 0 already moved the function to `payloadFields.ts`.
|
|
- **2026-05-15 — Keep `project.completed` / `project.task.completed` as
|
|
deprecated accepted aliases**, not removed. The public enum gates
|
|
subscription create/update validation; removal would break stored
|
|
`webhook_subscriptions` rows and the OpenAPI contract even though project
|
|
webhooks never delivered before.
|
|
- **2026-05-15 — `PROJECT_TASK_UPDATED` emitted from
|
|
`updateTaskWithChecklist` AND the interactive tag path (F008).** The other
|
|
five task write paths (status/phase-move/reorder/reorder-in-status/
|
|
dependency) still have dedicated events or no webhook-relevant delta.
|
|
- **2026-05-15 — Tag changes trigger webhooks for project_task AND ticket
|
|
(F008), parity** (user-confirmed; largest of the three options). Emit
|
|
`PROJECT_TASK_UPDATED` / `TICKET_UPDATED` with `changes.tags` from the
|
|
interactive tag path. No new public events — reuses `*.updated`. Rationale:
|
|
the no-trigger gap is identical for tickets today; fixing only projects
|
|
would leave the product inconsistent.
|
|
|
|
## Discoveries (grounded against codebase)
|
|
|
|
- Phase 0 ALREADY IMPLEMENTED as unstaged changes; tests pass
|
|
(`payloadFields.test.ts` 2/2; `webhookDelivery.*` 3/3). It consolidated MORE
|
|
than the original draft: `payloadFields.ts` also owns
|
|
`payloadFieldsByEntitySchema`, `webhookEntityForEventType`, and the
|
|
projection helper (moved out of `webhookTicketPayload.ts`; deprecated
|
|
`projectTicketWebhookPayload` shim deleted). Treat as precondition only.
|
|
- `ALWAYS_INCLUDED_KEYS_BY_ENTITY` shipped as
|
|
`... as const satisfies Record<WebhookPayloadEntity, readonly string[]>`
|
|
(strict). Keep it — compiler forces the `project` key when registry grows.
|
|
- Tag system (`shared/models/tagModel.ts`): `tagged_type` enum =
|
|
`['client','contact','project_task','document','knowledge_base_article']`.
|
|
→ NO `project` type. Project payload ships without `tags`. Task payload tags
|
|
via `'project_task'`. (Original draft had this inverted.)
|
|
- `projectActions.ts` emits: PROJECT_CREATED :986, PROJECT_ASSIGNED :1052,
|
|
PROJECT_STATUS_CHANGED :1089, PROJECT_UPDATED :1101, PROJECT_CLOSED :1532.
|
|
- Task mutation entry points in `packages/projects/src/actions/projectTaskActions.ts`:
|
|
`updateTaskWithChecklist` :505 (← emit PROJECT_TASK_UPDATED here only),
|
|
`updateTaskStatus` :688, `moveTaskToPhase` :1559, `reorderTask` :2102,
|
|
`reorderTasksInStatus` :2178, `updateTaskDependency` :2478.
|
|
- Current public enum (`webhookSchemas.ts:40-45`): project.created,
|
|
project.updated, project.completed, project.task.created,
|
|
project.task.updated, project.task.completed. Missing:
|
|
project.status_changed, project.assigned, project.closed,
|
|
project.task.status_changed, project.task.assigned.
|
|
- OpenAPI route now derives the per-entity payload schema from
|
|
`WEBHOOK_PAYLOAD_FIELDS_BY_ENTITY` → adding `project` auto-extends docs.
|
|
- F008 grounding: `packages/tags/src/actions/tagActions.ts` emits only
|
|
`TAG_DEFINITION_UPDATED` (lines 283, 831 — rename/recolor, not per-entity).
|
|
Entry points: `createTag` :101, `deleteTag` :335 (interactive single),
|
|
`createTagsForEntity` :534 + `createTagsForEntityWithTransaction` :571
|
|
(bulk, creation-time), `deleteAllTagsByText` :923 (bulk by text).
|
|
- `TICKET_UPDATED` internal event exists (eventBusSchema.ts:160, schema :785);
|
|
ticket webhook builder already attaches `changes` when
|
|
`eventType === 'TICKET_UPDATED'` (webhookTicketPayload.ts:143-144) via
|
|
`normalizeChanges` (:369). → ticket side of F008 needs ZERO builder change,
|
|
only event emission. F005 must mirror this `changes` attach for
|
|
`PROJECT_TASK_UPDATED`.
|
|
- ⚠️ DOUBLE-FIRE GOTCHA (F008): `TaskForm.tsx:910` calls
|
|
`createTagsForEntity(taskId,'project_task',pendingTags)` right after the
|
|
task is created (PROJECT_TASK_CREATED already fired). Emitting from the bulk
|
|
create path would double-fire CREATED + spurious UPDATED on every
|
|
create-with-tags. → emit only from interactive single-tag mutations, never
|
|
the bulk creation-time path. Same caution for ticket create-with-tags.
|
|
|
|
## Open questions / to verify during impl
|
|
|
|
- Task webhook `url` route shape: confirm `/msp/projects/:id?taskId=:taskId`
|
|
against the Next.js app router before shipping F005.
|
|
- Confirm an existing `normalizeChanges`-style helper in the project actions to
|
|
reuse for `changes` (do not roll a new diff util).
|
|
|
|
## Loop runbook
|
|
|
|
- Feature order (array order in features.json) =
|
|
F001(verify/commit precondition) → F002 → F003 → F008 → F004 → F005 →
|
|
F006 → F007. Each feature = one commit. F008 runs 4th (needs F003's
|
|
PROJECT_TASK_UPDATED; ticket side uses existing TICKET_UPDATED).
|
|
- Per-feature gate before flipping `implemented:true`:
|
|
- `cd server && npx vitest run <relevant specs> --coverage=false`
|
|
- workspace type-check.
|
|
- Useful commands:
|
|
- `cd /Users/natalliabukhtsik/Desktop/Desktop/projects/alga-psa/server && npx vitest run src/lib/webhooks/__tests__/payloadFields.test.ts --coverage=false`
|
|
- `... npx vitest run src/test/integration/webhookDelivery.entityIdFilter.test.ts src/test/integration/webhookDelivery.tenantIsolation.test.ts --coverage=false`
|
|
- ⚠️ Repo policy: do NOT stage/commit/push without explicit user request,
|
|
even inside the loop. The loop implements + verifies; the user commits.
|
|
|
|
## Status log
|
|
|
|
- 2026-05-15: Plan created (PRD + 7 features + 25 tests). Phase 0 done
|
|
(F001 implemented:true, uncommitted). Phase 1 not started.
|
|
- 2026-05-15: Added F008 (tag-change webhooks, projects + tickets parity) per
|
|
user decision → 8 features + 30 tests. Runs 4th in execution order.
|
|
- 2026-05-15: Completed F002. Renamed `projectWebhookPayload` to
|
|
`applyPayloadAllowlist` in `payloadFields.ts`, updated the ticket webhook
|
|
subscriber and payload-field tests, and added the optional
|
|
`extraAlwaysIncluded` parameter for future task payload projection.
|
|
Verification:
|
|
`cd server && npx vitest run src/lib/webhooks/__tests__/payloadFields.test.ts --coverage=false`
|
|
(4/4) and
|
|
`cd server && npx vitest run src/test/integration/webhookDelivery.entityIdFilter.test.ts src/test/integration/webhookDelivery.tenantIsolation.test.ts --coverage=false`
|
|
(3/3). Source search with coverage excluded has no remaining
|
|
`projectWebhookPayload` references. `npm run typecheck` OOMed under the
|
|
default Node heap; `NODE_OPTIONS='--max-old-space-size=8192' npm run typecheck`
|
|
passed and also satisfies T003.
|
|
- 2026-05-15: Completed F003. Added canonical `PROJECT_TASK_UPDATED` schema
|
|
with `projectTaskId` + `phaseId`, exported `ProjectTaskUpdatedEvent`, and
|
|
emit it from `updateTaskWithChecklist` only when
|
|
`buildProjectTaskWebhookChanges(...)` returns a non-empty diff. Extracted the
|
|
diff builder to `packages/projects/src/lib/projectTaskWebhookChanges.ts` so
|
|
date normalization and no-op behavior are unit tested without a DB/auth
|
|
harness. Added a contract test proving the five out-of-scope task mutation
|
|
entry points do not emit `PROJECT_TASK_UPDATED`. Verification:
|
|
`cd server && npx vitest run ../packages/event-schemas/src/schemas/eventBusSchema.projectTaskUpdated.test.ts ../packages/projects/src/lib/projectTaskWebhookChanges.test.ts ../packages/projects/src/actions/projectTaskWebhookUpdated.contract.test.ts --coverage=false`
|
|
(9/9) and
|
|
`cd server && NODE_OPTIONS='--max-old-space-size=8192' npm run typecheck`.
|
|
- 2026-05-15: Completed F008. Interactive `createTag` / `deleteTag` now
|
|
snapshots unique tag text sets before and after mutation and publishes
|
|
entity update events only when the set changes. `project_task` resolves
|
|
`{ projectId, phaseId }` and emits `PROJECT_TASK_UPDATED`; `ticket` emits
|
|
`TICKET_UPDATED`; both carry `changes.tags`. Added
|
|
`suppressEntityUpdateEvent` for `createTag` and use it from
|
|
`createTagsForEntity` so initial tag application does not double-fire;
|
|
`createTagsForEntityWithTransaction` remains bulk-only and never calls the
|
|
entity update publisher. Added a ticket payload regression proving
|
|
`TICKET_UPDATED` tag diffs reach `payload.changes`. Verification:
|
|
`cd server && npx vitest run ../packages/tags/src/actions/tagActions.webhookEmission.contract.test.ts src/lib/eventBus/subscribers/webhook/__tests__/webhookTicketPayload.test.ts --coverage=false`
|
|
(8/8) and
|
|
`cd server && NODE_OPTIONS='--max-old-space-size=8192' npm run typecheck`.
|
|
- 2026-05-15: Completed F004. Added project and project-task public events to
|
|
`SUPPORTED_WEBHOOK_EVENTS`, `webhookEventTypeSchema`, and the OpenAPI route
|
|
event enum, preserving `project.completed` as the deprecated
|
|
`project.closed` compatibility alias. Added `WEBHOOK_PROJECT_PAYLOAD_FIELDS`
|
|
and the single `project` payload entity; `project_id` is excluded from the
|
|
selectable list and retained via `ALWAYS_INCLUDED_KEYS_BY_ENTITY.project`.
|
|
Task-only `tags` is present in the combined project field list, with no
|
|
project-level tag field in the future project payload. Verification:
|
|
`cd server && npx vitest run src/lib/webhooks/__tests__/payloadFields.test.ts src/lib/api/schemas/__tests__/webhookSchemas.test.ts src/lib/actions/__tests__/webhookActions.supportedEvents.test.ts --coverage=false`
|
|
(7/7) and
|
|
`cd server && NODE_OPTIONS='--max-old-space-size=8192' npm run typecheck`.
|
|
- 2026-05-15: Completed F005. Added `webhookProjectEventMap.ts` with internal
|
|
to public project mappings, including `PROJECT_CLOSED` -> both
|
|
`project.closed` and deprecated `project.completed`. Added
|
|
`webhookProjectPayload.ts` for project and task payload builders, 60s/256
|
|
LRU caches, `project_task` tag resolution for task payloads, status-change
|
|
previous status enrichment, update `changes`, and uncached `phases` /
|
|
`task_counts` helpers. Task URL is implemented as
|
|
`/msp/projects/:projectId?taskId=:taskId`; no existing route reference was
|
|
found in source search, matching the PRD's accepted shape. Verification:
|
|
`cd server && npx vitest run src/lib/eventBus/subscribers/webhook/__tests__/webhookProjectEventMap.test.ts src/lib/eventBus/subscribers/webhook/__tests__/webhookProjectPayload.test.ts --coverage=false`
|
|
(7/7) and
|
|
`cd server && NODE_OPTIONS='--max-old-space-size=8192' npm run typecheck`.
|
|
- 2026-05-15: Completed F006. Added live
|
|
`projectWebhookSubscriber.ts`, registered it from subscriber index, and
|
|
widened `WebhookDeliveryQueue` typing from ticket-only to generic webhook
|
|
event/payload because the queue was already runtime entity-agnostic.
|
|
Project events filter on `projectId`; task events filter on
|
|
`projectTaskId`/`taskId` and project allowlist projection passes
|
|
`extraAlwaysIncluded:['task_id']`. Project `phases` and `task_counts`
|
|
opt-ins are fetched lazily once per event and reused for all matching
|
|
subscribers. Verification:
|
|
`cd server && npx vitest run src/lib/eventBus/subscribers/__tests__/projectWebhookSubscriber.test.ts src/lib/eventBus/subscribers/__tests__/subscriberIndex.projectWebhook.test.ts src/test/integration/webhookDelivery.entityIdFilter.test.ts src/test/integration/webhookDelivery.tenantIsolation.test.ts --coverage=false`
|
|
(6/6) and
|
|
`cd server && NODE_OPTIONS='--max-old-space-size=8192' npm run typecheck`.
|
|
- 2026-05-15: Completed F007. Added
|
|
`webhookDelivery.projectWebhooks.test.ts` covering project.created projection
|
|
with `phases`, project.task.updated tag-only delivery with `task_id`
|
|
retained, and ticket.updated tag-only parity. The "integration" style matches
|
|
the existing webhook delivery integration tests in this repo: subscriber
|
|
registration, event dispatch, mocked model/payload builders, and queue job
|
|
assertions rather than a live HTTP server/DB row. Verification:
|
|
`cd server && npx vitest run src/test/integration/webhookDelivery.projectWebhooks.test.ts src/test/integration/webhookDelivery.entityIdFilter.test.ts src/test/integration/webhookDelivery.tenantIsolation.test.ts src/lib/eventBus/subscribers/webhook/__tests__/webhookProjectEventMap.test.ts src/lib/eventBus/subscribers/webhook/__tests__/webhookProjectPayload.test.ts src/lib/eventBus/subscribers/__tests__/projectWebhookSubscriber.test.ts src/lib/eventBus/subscribers/__tests__/subscriberIndex.projectWebhook.test.ts src/lib/webhooks/__tests__/payloadFields.test.ts --coverage=false`
|
|
(20/20) and
|
|
`cd server && NODE_OPTIONS='--max-old-space-size=8192' npm run typecheck`.
|