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
17 KiB
17 KiB
Scratchpad — Ticket Contact Commentors + Inbound Email Contact Authorship
- Plan slug:
contact-commentors-inbound-email - Created:
2026-02-11
What This Is
Keep a lightweight, continuously-updated log of discoveries and decisions made while implementing this plan.
Prefer short bullets. Append new entries as you learn things, and also update earlier notes when a decision changes or an open question is resolved.
Decisions
- (2026-02-11) Draft approach: represent contact-only comment authorship by adding
comments.contact_id(tenant-scoped FK tocontacts.contact_name_id) rather than creating a new author enum state. - (2026-02-11) Preserve current semantic meaning of customer-authored comments as
author_type=clientfor response-state transitions;contact_idcarries identity whenuser_idis absent. - (2026-02-11) Keep scope focused on core behavior (data model + comment creation + rendering + inbound email wiring + tests), no operational/observability extras unless requested.
- (2026-02-11) No mandatory historical backfill in phase 1; additive behavior for new/updated comments first.
- (2026-02-11) Canonical authorship contract is now explicit in shared types via
CommentAuthorship(author_type+ nullableuser_id+ nullablecontact_id), andICommentmirrors this nullable dual-link model. - (2026-02-11) Shared comment typing extension is implemented in
packages/types/src/interfaces/comment.interface.ts; downstream loaders/components can now consumecomment.contact_idwithout ad-hoc casting.
Discoveries / Constraints
- (2026-02-11)
commentspreviously had contact columns, but they were removed inserver/migrations/20250217202553_drop_contact_columns.cjs. - (2026-02-11)
TicketModel.createCommentcurrently mapsauthor_type=contactto DBauthor_type=clientand only persistsuser_id; no contact identity is stored today (shared/models/ticketModel.ts). - (2026-02-11) Inbound new-ticket flow already resolves contact and optional client user (
findContactByEmail) and setsauthor_idonly when user exists; reply paths do not currently resolve contact (shared/services/email/processInboundEmailInApp.ts). - (2026-02-11) MSP and client-portal ticket details render comments from
comments+userMapsourced fromusers; comments with nouser_idrender asUnknown User(packages/tickets/src/components/ticket/CommentItem.tsx,packages/tickets/src/actions/optimizedTicketActions.ts,packages/client-portal/src/actions/client-portal-actions/client-tickets.ts). - (2026-02-11) Ticket comment API schema currently expects
created_byas UUID in responses, which is brittle for contact-only authors ifuser_idis null (server/src/lib/api/schemas/ticket.ts). - (2026-02-11)
packages/typeshadIComment.user_id?: stringand no contact author linkage field; adding nullableuser_id+contact_idin the base interface is required before loader/UI/API updates. - (2026-02-11) Added migration scaffold
server/migrations/20260211190000_add_comments_contact_id.cjsto reintroduce nullablecomments.contact_id; FK/index are intentionally separated into the next step for checklist traceability. - (2026-02-11) Migration
20260211190000_add_comments_contact_id.cjsnow includescomments_tenant_contact_id_fk(tenant, contact_id -> contacts.tenant, contact_name_id) andcomments_tenant_contact_id_idxfor bounded comment-author lookups. - (2026-02-11)
shared/models/ticketModel.tscomment creation schema and input types now accept optionalcontact_idwith UUID validation. - (2026-02-11)
TicketModel.createCommentnow performs tenant-scoped contact validation (contacts.contact_name_id+tenant) and fails fast whencontact_idis missing/out-of-tenant. - (2026-02-11)
TicketModel.createCommentinsert payload now writescomments.contact_iddirectly, enabling contact-only comments and dual-link comments (user_id+contact_id). - (2026-02-11) Shared comment creation now updates
tickets.response_statetoawaiting_internalfor non-internalauthor_type=contactcomments; internal comments remain unchanged. - (2026-02-11)
createCommentFromEmailcontract inshared/workflow/actions/emailWorkflowActions.tsnow accepts optionalcontact_idto support contact-only comment authorship from inbound paths. - (2026-02-11)
createCommentFromEmailnow forwardscontact_idintoTicketModel.createComment, so inbound email comments can persist contact authorship linkage. - (2026-02-11) New-ticket inbound path now passes
contact_idintocreateCommentFromEmailwhenever sender contact is matched, regardless of whether a client user was resolved. - (2026-02-11) Reply-token inbound path now resolves sender contact once per email and forwards both
contact_idand resolvedauthor_id(when present) to comment creation. - (2026-02-11) Thread-header inbound reply path reuses the same sender-contact resolution and now forwards
contact_id/author_idto comment creation. - (2026-02-11) End-to-end inbound paths now support dual linkage (
author_id+contact_id) when a matched contact has an associated client user, preserving user identity while keeping explicit contact authorship. - (2026-02-11) Workflow action schemas now expose
contact_idincreate_comment_from_email(V2 runtime + legacy action registry), so workflow definitions can pass contact authorship explicitly. - (2026-02-11) Workflow runtime implementations now pass
contact_idthrough to shared comment creation (create_comment_from_email,create_ticket_with_initial_comment, and parsed-reply comment path with sender contact resolution fallback). - (2026-02-11) MSP consolidated ticket loader now builds a
contactMapkeyed bycontact_name_idfor allcomments.contact_idvalues, giving conversation rendering a first-class contact author source alongsideuserMap. - (2026-02-11) Client-portal ticket details loader now also returns
contactMapfromcomments.contact_id, keeping author resolution parity between MSP and portal views. - (2026-02-11) Added
packages/tickets/src/lib/commentAuthorResolution.tsas the shared author resolver with deterministic precedence: user author, then contact author, then unknown fallback. - (2026-02-11)
CommentItemnow resolves authors via shared helper +contactMap, rendering contact-authored comments with contact name/email/avatar (without requiringuser_id). - (2026-02-11) Unknown-user fallback is now explicitly constrained to comments where neither
userMap[comment.user_id]norcontactMap[comment.contact_id]resolves, viaresolveCommentAuthorprecedence. - (2026-02-11) API comment payloads now support contact-authored rows by making
created_bynullable and adding contact author fields (author_contact_id/name/email) fromTicketService.getTicketComments+ticketCommentResponseSchema. - (2026-02-11)
ticketEmailSubscribernow resolves comment author identity from persistedcommentsrow (user_id/contact_id+ emails) and excludes author recipients by both user-id and email/contact in comment fan-out paths. - (2026-02-11) Added focused unit coverage for
processInboundEmailInAppcovering contact-only authorship in new-ticket, reply-token, and thread-header paths (shared/services/email/__tests__/processInboundEmailInApp.test.ts).
Commands / Runbooks
- (2026-02-11) Locate existing comments/inbound-email/authorship behavior:
rg -n "author_type|author_id|Unknown User|processInboundEmailInApp|createCommentFromEmail" shared server packages -g"*.ts" -g"*.tsx"
- (2026-02-11) Review current ticket conversation rendering path:
sed -n '1,360p' packages/tickets/src/components/ticket/CommentItem.tsxsed -n '180,320p' packages/tickets/src/actions/optimizedTicketActions.ts
- (2026-02-11) Review inbound email creation/reply logic:
sed -n '1,760p' shared/services/email/processInboundEmailInApp.tssed -n '680,820p' shared/workflow/actions/emailWorkflowActions.ts
- (2026-02-11) Review legacy migration history for comments/contact linkage:
rg -n "comments|contact_id|contact_name_id|author_type" server/migrations -g"*.cjs"
- (2026-02-11) Validate canonical type updates:
npx vitest run packages/types/src/interfaces/comment.interface.typecheck.test.ts
- (2026-02-11) Verify inbound email comment wiring after runtime action changes:
npx vitest run shared/workflow/actions/__tests__/emailWorkflowActions.responseSource.test.ts shared/services/email/__tests__/processInboundEmailInApp.test.ts
Links / References
- Plan folder:
ee/docs/plans/2026-02-11-contact-commentors-inbound-email - Existing related plan:
ee/docs/plans/2026-01-24-inbound-email-in-app-processing - Existing related plan:
ee/docs/plans/2026-02-05-ticket-response-source - Core files:
shared/services/email/processInboundEmailInApp.tsshared/workflow/actions/emailWorkflowActions.tsshared/models/ticketModel.tspackages/tickets/src/actions/optimizedTicketActions.tspackages/tickets/src/components/ticket/TicketConversation.tsxpackages/tickets/src/components/ticket/CommentItem.tsxpackages/client-portal/src/actions/client-portal-actions/client-tickets.tsserver/src/lib/api/services/TicketService.tsserver/src/lib/api/schemas/ticket.tsserver/migrations/20250217202553_drop_contact_columns.cjs
Open Questions
- Should contact-authored comments display
Name (Contact)or onlyNamewith contact avatar semantics? - Should we always persist
contact_idalongsideuser_idfor client users when available, or only for contact-only authors? - For reply flows with unresolved sender contact but resolved ticket, should we fallback to ticket contact or keep unknown?
- Do we want API responses to expose additive flat fields or a nested
authorobject for future-proofing? - Should client portal display for contact-authored comments differ from MSP display in any way?
- (2026-02-11) Added cross-layer verification for contact-authored comment support: integration assertions in inbound webhook tests, UI-level author resolution tests, and API schema tests for nullable
created_by+ contact author fields. - (2026-02-11) T001 complete: added migration contract test asserting
comments.contact_idis created as nullable UUID (server/src/test/unit/migrations/commentsContactAuthorshipMigration.test.ts). - (2026-02-11) T002 complete: migration contract test now verifies tenant-scoped FK/index wiring for
comments.contact_id -> contacts.contact_name_id. - (2026-02-11) T003 complete: migration contract test now asserts down-path cleanup removes FK, index, and
contact_idcolumn. - (2026-02-11) T004 complete: added
TicketModel.createCommentunit suite covering contact author input acceptance withcontact_idand noauthor_id. - (2026-02-11) T005 complete:
TicketModel.createCommenttest now asserts invalidcontact_idformat is rejected by input validation. EOF && git add ee/docs/plans/2026-02-11-contact-commentors-inbound-email/tests.json ee/docs/plans/2026-02-11-contact-commentors-inbound-email/SCRATCHPAD.md && git commit -m "test(T005): reject malformed contact_id in createComment"- (2026-02-11) T006 complete:TicketModel.createCommenttests cover tenant-safety by rejecting missing/cross-tenantcontact_idlookups. - (2026-02-11) T007 complete:
TicketModel.createCommentpersistence test asserts insertedcommentsrow containscontact_idfor contact-authored comments. EOF && git add ee/docs/plans/2026-02-11-contact-commentors-inbound-email/tests.json ee/docs/plans/2026-02-11-contact-commentors-inbound-email/SCRATCHPAD.md && git commit -m "test(T007): verify contact_id persistence on comment insert"- (2026-02-11) T008 complete:TicketModel.createCommenttests now verify dual-link persistence when bothauthor_idandcontact_idare supplied. - (2026-02-11) T009 complete: unit tests assert public contact-authored comments set ticket
response_statetoawaiting_internal. - (2026-02-11) T010 complete: unit tests confirm internal contact-authored comments do not change ticket
response_state. - (2026-02-11) T011 complete:
createCommentFromEmailunit coverage now verifiescontact_idis forwarded intoTicketModel.createComment. - (2026-02-11) T012 complete: unit tests now assert dual forwarding of
author_idandcontact_idfromcreateCommentFromEmail. - (2026-02-11) T013 complete: unit tests cover new-ticket inbound flow with matched contact/no user and assert
contact_idis set whileauthor_idis omitted. - (2026-02-11) T014 complete: unit tests assert new-ticket inbound flow with matched contact+user forwards both
contact_idandauthor_id. - (2026-02-11) T015 complete: added dedicated unit coverage for unmatched new-ticket sender fallback (no
contact_id, system author path preserved). - (2026-02-11) T016 complete: unit tests cover reply-token threading with contact-only sender and assert
contact_idpropagation. - (2026-02-11) T017 complete: additional unit coverage verifies reply-token path forwards both
author_idandcontact_idfor contact+user matches. - (2026-02-11) T018 complete: unit tests validate thread-header reply path sets
contact_idfor contact-only senders. - (2026-02-11) T019 complete: added unit assertion that thread-header reply path without matched sender contact preserves fallback (
contact_idomitted). - (2026-02-11) T020 complete: runtime action registration tests now validate
create_comment_from_emailinput schema acceptscontact_id. - (2026-02-11) T021 complete: runtime registration tests assert the initial ticket+comment action forwards
targetContactIdintocreateCommentFromEmail. - (2026-02-11) T022 complete: runtime registration tests assert
create_comment_from_emailhandler forwardscontact_idinto the shared email action call. - (2026-02-11) T023 complete: added MSP consolidated data contract tests confirming comment-loading path carries
contact_idsupport in returned conversation payloads. - (2026-02-11) T024 complete: MSP contract tests now assert contact author resolution map (
contactMap) is built from comment contacts. - (2026-02-11) T025 complete: added client-portal ticket details contract test ensuring contact author resolution data is assembled in
client-tickets. - (2026-02-11) T026 complete:
resolveCommentAuthorunit tests verify precedence order user → contact → unknown. - (2026-02-11) T027 complete: added
CommentItemcontract tests confirming contact-authored comments resolve/render contact display name instead of unknown fallback. - (2026-02-11) T028 complete:
CommentItemcontract tests verify contact email rendering path (mailto:link) when author email is available. - (2026-02-11) T029 complete:
CommentItemcontract tests verifyContactAvatarbranch is used for contact-authored comments. - (2026-02-11) T030 complete:
CommentItemcontract tests preserve Unknown User fallback branch for unresolved user/contact authors. - (2026-02-11) T031 complete:
CommentItemcontract tests confirm edit/delete permission gate remains tied toconversation.user_idownership. - (2026-02-11) T032 complete: added TicketService contract tests asserting
getTicketCommentsexposes contact author fields and nullablecreated_bymapping. - (2026-02-11) T033 complete: API schema unit tests validate contact-authored ticket comment payloads without requiring
created_byUUID. - (2026-02-11) T034 complete: response-shape test now covers schema-validated contact-authored comment payload acceptance for ticket comments API outputs.
- (2026-02-11) T035 complete: added ticket-email-subscriber contract tests asserting contact-author exclusion gates in recipient fan-out logic.
- (2026-02-12) T036 complete: existing inbound integration test
Contact match: sender email matches existing contact and ticket uses contact's client_id/contact_idalready asserts Google contact-only authorship (author_type=client,contact_idset,user_idnull). - (2026-02-12) Integration test execution in this worktree currently cannot establish DB auth (
password authentication failed for user "postgres"), so webhook integration cases are validated by test code inspection + committed assertions but cannot be executed locally end-to-end. - (2026-02-12) T037 complete: added Microsoft-provider integration case in
inboundEmailInApp.webhooks.integration.test.tsasserting matched contact-without-user creates new ticket comment withauthor_type=client,contact_idset, anduser_idnull. - (2026-02-12) T038 complete: added reply-token integration case for matched contact-without-user (
provider: microsoft) asserting persisted reply comment is contact-authored (author_type=client,contact_idset,user_idnull). - (2026-02-12) T039 complete: strengthened matched client-user inbound integration assertion to require both preserved user linkage (
user_id) and contact linkage (contact_id) on the created comment. - (2026-02-12) T040 complete: unmatched-sender integration assertions now verify comment identity links remain unresolved (
contact_idnull,user_idnull) with system/internal authoring, preserving unknown-user fallback behavior.