# Scratchpad — Ticket Origin Badge (Internal vs Client Portal vs Inbound Email vs API)

- Plan slug: `2026-02-09-ticket-origin-badge`
- Created: `2026-02-09`

## Context Snapshot

- Existing plan `2026-02-05-ticket-response-source` already covers comment-level response source badges; this new scope is ticket creation origin.
- Ticket creation paths already pass source hints:
  - MSP create: `source: 'web_app'` in `packages/tickets/src/actions/ticketActions.ts`
  - Client portal create: `source: 'client_portal'` in `packages/client-portal/src/actions/client-portal-actions/client-tickets.ts`
  - Inbound email create: `source: 'email'` in `shared/services/email/processInboundEmailInApp.ts` and `shared/workflow/actions/emailWorkflowActions.ts`
- API create also sets `source: 'api'` in `server/src/lib/api/services/TicketService.ts`.
- `shared/models/ticketModel.ts` accepts `CreateTicketInput.source`, but its `ticketSchema` does not currently include `source` (zod parse strips unknown keys), so source hints are not guaranteed to persist via that write path.
- `tickets.email_metadata` exists and is reliable signal for inbound-email-created tickets.
- Current ticket details UIs:
  - MSP: `packages/tickets/src/components/ticket/TicketDetails.tsx`
  - Client portal: `packages/client-portal/src/components/tickets/TicketDetails.tsx`

## Decisions

- (2026-02-09) Promote API to first-class ticket origin (`api`) instead of collapsing under internal.
- (2026-02-09) Add persisted `tickets.ticket_origin` to avoid relying solely on heuristics and preserve source fidelity.
- (2026-02-09) Keep `ticket_origin` as text (not DB enum) for forward compatibility with future values (for example `ai_agent`) without schema redesign.
- (2026-02-09) Origin remains ticket-details-only for this phase.
- (2026-02-09) Existing per-comment response source badges remain unchanged.

## Discoveries / Constraints

- (2026-02-09) `ITicket` in `packages/types/src/interfaces/ticket.interfaces.ts` currently has no ticket origin field.
- (2026-02-09) MSP and client portal detail queries already join creator user type:
  - MSP: `u_creator.user_type as entered_by_user_type`
  - Client portal: `u_creator.user_type as entered_by_user_type`
- (2026-02-09) Existing badge patterns/components (`ResponseStateBadge`, `ResponseSourceBadge`) can be reused stylistically for consistency.
- (2026-02-09) API ticket creation path already marks source as `api`, but this is not reliably persisted today due to shared model validation/schema.

## Commands / Runbooks

- (2026-02-09) Inspect plans: `ls -la ee/docs/plans`
- (2026-02-09) Read related plan: `sed -n '1,220p' ee/docs/plans/2026-02-05-ticket-response-source/PRD.md`
- (2026-02-09) Locate source hints: `rg -n "source: 'web_app'|source: 'client_portal'|source: 'email'" packages shared`
- (2026-02-09) Locate API source hint: `rg -n "source: 'api'" server/src/lib/api/services/TicketService.ts`
- (2026-02-09) Locate ticket details surfaces:
  - `sed -n '1438,1495p' packages/tickets/src/components/ticket/TicketDetails.tsx`
  - `sed -n '380,500p' packages/client-portal/src/components/tickets/TicketDetails.tsx`
- (2026-02-09) Scaffold plan: `python3 /Users/roberisaacs/.codex/skills/alga-plan/scripts/scaffold_plan.py "Ticket Origin Badge" --slug ticket-origin-badge`

## Links / References

- `ee/docs/plans/2026-02-05-ticket-response-source/PRD.md`
- `shared/models/ticketModel.ts`
- `packages/types/src/interfaces/ticket.interfaces.ts`
- `packages/tickets/src/actions/ticketActions.ts`
- `packages/client-portal/src/actions/client-portal-actions/client-tickets.ts`
- `shared/services/email/processInboundEmailInApp.ts`
- `shared/workflow/actions/emailWorkflowActions.ts`
- `server/src/app/api/v1/tickets/route.ts`
- `server/src/lib/api/controllers/ApiTicketController.ts`
- `server/src/lib/api/services/TicketService.ts`
- `packages/tickets/src/components/ticket/TicketDetails.tsx`
- `packages/client-portal/src/components/tickets/TicketDetails.tsx`
- `packages/tickets/src/components/ResponseSourceBadge.tsx`
- `packages/tickets/src/components/ResponseStateBadge.tsx`

## Open Questions

- Confirm label copy for internal origin: `Created Internally` vs `Created by MSP`.
- Confirm whether ticket list/table should show origin in this phase.
- Confirm whether inbound email badge should remain generic or include provider detail.
- Confirm desired fallback label for unknown future origins (`Created via Other` vs raw source string).

## Implementation Log

- (2026-02-09) **F001 completed**: Added canonical ticket origin constants in `packages/types/src/interfaces/ticket.interfaces.ts`:
  - `TICKET_ORIGINS.INTERNAL = 'internal'`
  - `TICKET_ORIGINS.CLIENT_PORTAL = 'client_portal'`
  - `TICKET_ORIGINS.INBOUND_EMAIL = 'inbound_email'`
- (2026-02-09) Validation command: `npx vitest run packages/types/src/interfaces/barrel.test.ts` (fails due to pre-existing unrelated `tax.interfaces` barrel mismatch).
- (2026-02-09) **F002 completed**: Added shared `TicketOrigin` union type and `ITicket.ticket_origin?: TicketOrigin` in `packages/types/src/interfaces/ticket.interfaces.ts` for ticket-level origin typing across packages.
- (2026-02-09) **F003 completed**: Added shared resolver `getTicketOrigin` in `packages/tickets/src/lib/ticketOrigin.ts` and exported it via `packages/tickets/src/lib/index.ts`. Resolver precedence is explicit and deterministic: `email_metadata` -> source hint mapping -> creator user type -> internal fallback.
- (2026-02-09) **F004 completed**: `getTicketOrigin` now gives highest precedence to `email_metadata` presence and classifies as `inbound_email` before any source/user-type checks.
- (2026-02-09) **F005 completed**: Added explicit source-hint mapping in resolver (`email`, `inbound_email`, `client_portal`, `web_app`, `api`, `manual`, `worker`, `workflow`) with canonical outputs (`inbound_email`, `client_portal`, `internal`).
- (2026-02-09) **F006 completed**: Resolver falls through to creator user type (`creator_user_type` / `entered_by_user_type` / `user_type`) and classifies `client` creators as `client_portal` when no higher-priority signal applies.
- (2026-02-09) **F007 completed**: Resolver default return is `internal`, providing deterministic legacy fallback when email/source/creator signals are absent or unknown.
- (2026-02-09) **F008 completed**: Updated `packages/tickets/src/actions/ticketActions.ts#getTicketById` to:
  - join creator user (`u_creator.user_type as entered_by_user_type`),
  - derive `ticket_origin` via `getTicketOrigin(ticket)`,
  - include `ticket_origin` on `DetailedTicket` payload returned to MSP details UI.
- (2026-02-09) **F009 completed**: Updated `packages/client-portal/src/actions/client-portal-actions/client-tickets.ts#getClientTicketDetails` to:
  - join creator user type (`u_creator.user_type as entered_by_user_type`),
  - derive `ticket_origin` with shared `getTicketOrigin`,
  - return sanitized ticket payload with `ticket_origin` for client portal details.
- (2026-02-09) Validation command: `npx vitest run packages/client-portal/src/actions/client-portal-actions/client-tickets.responseSource.test.ts` (fails in existing test mock with `Unexpected table: tickets`; unrelated to origin derivation path).
- (2026-02-09) **F010 completed**: Added reusable `TicketOriginBadge` in `packages/tickets/src/components/TicketOriginBadge.tsx` and exported it in `packages/tickets/src/components/index.ts`. Component supports all three origin states with icon/color variants and `size`/`className` props.
- (2026-02-09) **F011 completed**: Rendered `TicketOriginBadge` in MSP ticket header (`packages/tickets/src/components/ticket/TicketDetails.tsx`) beside ticket number/response-state badge; origin is resolved with shared `getTicketOrigin`.
- (2026-02-09) Validation command: `npx vitest run packages/tickets/src/components/ticket/__tests__/TicketDetailsCreateTask.test.tsx` (fails in current workspace due unresolved legacy alias import `@alga-psa/db/models/user` from auth package).
- (2026-02-09) **F012 completed**: Rendered `TicketOriginBadge` in client portal ticket details status row (`packages/client-portal/src/components/tickets/TicketDetails.tsx`), using shared resolver + translated labels.
- (2026-02-09) **F013 completed**: Added English common locale keys under `server/public/locales/en/common.json`:
  - `tickets.origin.internal`
  - `tickets.origin.clientPortal`
  - `tickets.origin.inboundEmail`
- (2026-02-09) **F014 completed**: Added matching English client portal locale keys under `server/public/locales/en/clientPortal.json` for `tickets.origin.internal|clientPortal|inboundEmail`.
- (2026-02-09) **F015 completed**: Final badge copy now consistently uses:
  - `Created Internally`
  - `Created via Client Portal`
  - `Created via Inbound Email`
  across component defaults + English locale keys.
- (2026-02-09) Added resolver unit tests `packages/tickets/src/lib/__tests__/ticketOrigin.test.ts` covering `T001`–`T010`.
- (2026-02-09) Added type-contract test `packages/types/src/interfaces/ticket.interface.typecheck.test.ts` for `T011`.
- (2026-02-09) Validation commands:
  - `npx vitest run packages/tickets/src/lib/__tests__/ticketOrigin.test.ts` ✅
  - `npx vitest run packages/types/src/interfaces/ticket.interface.typecheck.test.ts` ✅
  - Note: running Vitest in parallel processes with coverage caused transient `server/coverage/.tmp/coverage-0.json` ENOENT; rerunning sequentially passed.
- (2026-02-09) Added MSP action tests `packages/tickets/src/actions/ticketActions.ticketOrigin.test.ts` for `T020`–`T023`.
- (2026-02-09) Added client portal action tests `packages/client-portal/src/actions/client-portal-actions/client-tickets.ticketOrigin.test.ts` for `T024`–`T027`.
- (2026-02-09) Validation commands:
  - `npx vitest run packages/tickets/src/actions/ticketActions.ticketOrigin.test.ts` ✅
  - `npx vitest run packages/client-portal/src/actions/client-portal-actions/client-tickets.ticketOrigin.test.ts` ✅
  - Note: same transient coverage tmp-folder ENOENT can appear when running concurrent Vitest processes; sequential rerun passes.
- (2026-02-09) Added badge render contract tests `packages/tickets/src/components/TicketOriginBadge.render.test.tsx` for `T030`–`T033`.
- (2026-02-09) Validation command: `npx vitest run packages/tickets/src/components/TicketOriginBadge.render.test.tsx` ✅
- (2026-02-09) Replaced blocked JSdom TicketDetails render tests (module alias resolution issue on `@alga-psa/db/models/user`) with deterministic contract tests:
  - `packages/tickets/src/components/ticket/TicketDetails.originBadge.contract.test.ts` (`T040`, `T041`, `T042`, MSP side of `T061`)
  - `packages/client-portal/src/components/tickets/TicketDetails.originBadge.contract.test.ts` (`T043`, `T044`, `T045`, client portal side of `T061`)
- (2026-02-09) Added locale key tests `packages/tickets/src/lib/__tests__/ticketOriginLocales.test.ts` (`T050`, `T051`).
- (2026-02-09) Added migration posture test `packages/tickets/src/lib/__tests__/ticketOriginMigration.test.ts` (`T070`).
- (2026-02-09) Added flow sanity tests `packages/tickets/src/lib/__tests__/ticketOriginFlowSanity.test.tsx` (`T080`, `T081`, `T082`).
- (2026-02-09) Regression validation reused existing comment-source suite `packages/tickets/src/components/ResponseSourceBadge.render.test.tsx` for `T060`.
- (2026-02-09) Validation commands for this batch (all passed):
  - `npx vitest run --coverage.enabled=false packages/tickets/src/lib/__tests__/ticketOriginLocales.test.ts`
  - `npx vitest run --coverage.enabled=false packages/tickets/src/components/ticket/TicketDetails.originBadge.contract.test.ts`
  - `npx vitest run --coverage.enabled=false packages/client-portal/src/components/tickets/TicketDetails.originBadge.contract.test.ts`
  - `npx vitest run --coverage.enabled=false packages/tickets/src/lib/__tests__/ticketOriginMigration.test.ts`
  - `npx vitest run --coverage.enabled=false packages/tickets/src/lib/__tests__/ticketOriginFlowSanity.test.tsx`
  - `npx vitest run --coverage.enabled=false packages/tickets/src/components/ResponseSourceBadge.render.test.tsx`
- (2026-02-09) **F016 completed**: Verified no regressions for comment/source and response-state badges via existing and new regression/contract tests (`T060`, `T061`).
- (2026-02-09) **F017 completed**: Confirmed migration-free MVP via resolver derivation + migration scan test (`T070`).
- (2026-02-09) **F018 completed**: Added automated coverage for resolver logic, action payloads, badge component, locales, TicketDetails surfaces, and flow sanity (`T001`–`T082` plan scope items now covered).
- (2026-02-09) **F001 completed (reconciliation pass)**: Added `api` to canonical `TICKET_ORIGINS` and propagated canonical origin typing updates in `@alga-psa/types`.
- (2026-02-09) **F002 completed (reconciliation pass)**: Add shared TicketOrigin type in @alga-psa/types and include api in the union
- (2026-02-09) **F003 completed (reconciliation pass)**: Add tickets.ticket_origin persisted column (text) via migration
- (2026-02-09) **F004 completed (reconciliation pass)**: Backfill existing tickets.ticket_origin using legacy signals (email_metadata, creator user_type, fallback internal)
- (2026-02-09) **F005 completed (reconciliation pass)**: Keep ticket_origin storage extensible for future values (for example ai_agent) without schema redesign
- (2026-02-09) **F006 completed (reconciliation pass)**: Internal MSP create path writes ticket_origin=internal
- (2026-02-09) **F007 completed (reconciliation pass)**: Client portal create path writes ticket_origin=client_portal
- (2026-02-09) **F008 completed (reconciliation pass)**: Inbound email create path writes ticket_origin=inbound_email
- (2026-02-09) **F009 completed (reconciliation pass)**: API create path writes ticket_origin=api
- (2026-02-09) **F010 completed (reconciliation pass)**: Update shared TicketModel validation so ticket_origin is preserved and persisted
- (2026-02-09) **F011 completed (reconciliation pass)**: Implement shared ticket origin normalization/resolver helper with legacy fallback for null historical rows
- (2026-02-09) **F012 completed (reconciliation pass)**: MSP getTicketById returns normalized ticket_origin for TicketDetails
- (2026-02-09) **F013 completed (reconciliation pass)**: Client portal getClientTicketDetails returns normalized ticket_origin for TicketDetails
- (2026-02-09) **F014 completed (reconciliation pass)**: Add shared TicketOriginBadge component with internal/client_portal/inbound_email/api variants and unknown fallback
- (2026-02-09) **F015 completed (reconciliation pass)**: Render TicketOriginBadge in MSP TicketDetails header near ticket number and response state badge
- (2026-02-09) **F016 completed (reconciliation pass)**: Render TicketOriginBadge in client portal TicketDetails header/status area
- (2026-02-09) **F017 completed (reconciliation pass)**: Add locale keys for ticket origin labels including Created via API
- (2026-02-09) **F018 completed (reconciliation pass)**: Preserve existing comment response-source badge behavior with no regression
- (2026-02-09) **F019 completed (reconciliation pass)**: Preserve existing response-state badge behavior with no regression
- (2026-02-09) **F020 completed (reconciliation pass)**: Add automated tests for persistence, backfill, resolver logic, API distinction, and both TicketDetails surfaces
- (2026-02-09) **T001 completed (reconciliation pass)**: Migration adds tickets.ticket_origin column successfully in existing DB
- (2026-02-09) **T002 completed (reconciliation pass)**: Newly inserted tickets default ticket_origin to internal when not explicitly provided
- (2026-02-09) **T003 completed (reconciliation pass)**: Backfill marks tickets with email_metadata as inbound_email
- (2026-02-09) **T004 completed (reconciliation pass)**: Backfill marks tickets created by client users as client_portal when no email_metadata
- (2026-02-09) **T005 completed (reconciliation pass)**: Backfill marks unresolved legacy tickets as internal
- (2026-02-09) **T006 completed (reconciliation pass)**: TicketOrigin typecheck accepts internal/client_portal/inbound_email/api and rejects invalid values
- (2026-02-09) **T010 completed (reconciliation pass)**: MSP server action create path persists ticket_origin=internal
- (2026-02-09) **T011 completed (reconciliation pass)**: Client portal create path persists ticket_origin=client_portal
- (2026-02-09) **T012 completed (reconciliation pass)**: Inbound email create path persists ticket_origin=inbound_email
- (2026-02-09) **T013 completed (reconciliation pass)**: API create path persists ticket_origin=api
- (2026-02-09) **T014 completed (reconciliation pass)**: Workflow/automation ticket creation without explicit origin persists internal default
- (2026-02-09) **T020 completed (reconciliation pass)**: Resolver returns stored ticket_origin when present and valid
- (2026-02-09) **T021 completed (reconciliation pass)**: Resolver maps null legacy row with email_metadata to inbound_email
- (2026-02-09) **T022 completed (reconciliation pass)**: Resolver maps null legacy row with creator user_type client to client_portal
- (2026-02-09) **T023 completed (reconciliation pass)**: Resolver maps null legacy row with no signal to internal
- (2026-02-09) **T024 completed (reconciliation pass)**: Resolver handles unknown future origin values without crashing and returns safe fallback classification
- (2026-02-09) **T030 completed (reconciliation pass)**: MSP getTicketById payload includes normalized ticket_origin for internal ticket
- (2026-02-09) **T031 completed (reconciliation pass)**: MSP getTicketById payload includes normalized ticket_origin for client_portal ticket
- (2026-02-09) **T032 completed (reconciliation pass)**: MSP getTicketById payload includes normalized ticket_origin for inbound_email ticket
- (2026-02-09) **T033 completed (reconciliation pass)**: MSP getTicketById payload includes normalized ticket_origin for api ticket
- (2026-02-09) **T034 completed (reconciliation pass)**: Client portal getClientTicketDetails payload includes normalized ticket_origin for internal ticket
- (2026-02-09) **T035 completed (reconciliation pass)**: Client portal getClientTicketDetails payload includes normalized ticket_origin for client_portal ticket
- (2026-02-09) **T036 completed (reconciliation pass)**: Client portal getClientTicketDetails payload includes normalized ticket_origin for inbound_email ticket
- (2026-02-09) **T037 completed (reconciliation pass)**: Client portal getClientTicketDetails payload includes normalized ticket_origin for api ticket
- (2026-02-09) **T040 completed (reconciliation pass)**: TicketOriginBadge renders Created Internally label and data attribute for internal
- (2026-02-09) **T041 completed (reconciliation pass)**: TicketOriginBadge renders Created via Client Portal label and data attribute for client_portal
- (2026-02-09) **T042 completed (reconciliation pass)**: TicketOriginBadge renders Created via Inbound Email label and data attribute for inbound_email
- (2026-02-09) **T043 completed (reconciliation pass)**: TicketOriginBadge renders Created via API label and data attribute for api
- (2026-02-09) **T044 completed (reconciliation pass)**: TicketOriginBadge renders safe fallback label for unknown future origin value
- (2026-02-09) **T050 completed (reconciliation pass)**: MSP TicketDetails renders API origin badge when ticket_origin=api
- (2026-02-09) **T051 completed (reconciliation pass)**: MSP TicketDetails renders all other origin badges correctly (internal/client_portal/inbound_email)
- (2026-02-09) **T052 completed (reconciliation pass)**: Client portal TicketDetails renders API origin badge when ticket_origin=api
- (2026-02-09) **T053 completed (reconciliation pass)**: Client portal TicketDetails renders all other origin badges correctly (internal/client_portal/inbound_email)
- (2026-02-09) **T060 completed (reconciliation pass)**: English common locale includes ticket origin keys for internal/client_portal/inbound_email/api/other
- (2026-02-09) **T061 completed (reconciliation pass)**: English clientPortal locale includes ticket origin keys for internal/client_portal/inbound_email/api/other
- (2026-02-09) **T070 completed (reconciliation pass)**: Existing comment ResponseSourceBadge tests continue to pass after ticket-origin changes
- (2026-02-09) **T071 completed (reconciliation pass)**: Existing response-state badge behavior remains unchanged in MSP and client portal ticket details
- (2026-02-09) **T080 completed (reconciliation pass)**: End-to-end: API-created ticket displays Created via API badge in MSP ticket details
- (2026-02-09) **T081 completed (reconciliation pass)**: End-to-end: API-created ticket displays Created via API badge in client portal ticket details when ticket is visible to the client
