Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

113 KiB
Raw Permalink Blame History

Scratchpad — AlgaDesk Lightweight Help Desk Product Seam

  • Plan slug: 2026-05-05-algadesk-lightweight-helpdesk-product-seam
  • Created: 2026-05-05

What This Is

Working notes for the AlgaDesk product seam plan. Keep this updated as implementation discovers constraints, changes scope, or adds runbooks.

Decisions

  • (2026-05-05) AlgaDesk is an orthogonal product entitlement, not a new PSA tier. Rationale: product surface and price/package ladder should evolve independently.
  • (2026-05-05) AlgaDesk includes email-to-ticket and ticket reply/update by email. Rationale: a help desk without email is too weak as an MSP wedge product.
  • (2026-05-05) AlgaDesk includes ticket attachments and KB only, not full document management.
  • (2026-05-05) AlgaDesk includes free-form ticket creation only, not service request forms/catalog in v1.
  • (2026-05-05) AlgaDesk excludes SLA module in v1 while preserving ticket data-model compatibility for possible later lightweight targets.
  • (2026-05-05) Direct browser access behavior is mixed: branded upgrade boundaries for major human-facing PSA areas, product-denied/not-found for internal/API-only routes.
  • (2026-05-05) Current routes remain canonical for v1; /desk/* aliases can be added later.
  • (2026-05-05) Existing background workers/services may remain separate runtime processes for email. The seam is licensed product surface, not forcing all work into one process.
  • (2026-05-05) Product seam should be high quality via AlgaDesk-specific composition, not menu-only hiding.
  • (2026-05-05) Plan structure intentionally uses many feature checklist items (150+) to express intent and a smaller confidence-building test suite (~20) instead of comprehensive one-test-per-feature coverage.

Discoveries / Constraints

  • (2026-05-05) Existing tier model is rank-based around solo, pro, and premium; it does not model product surface.
  • (2026-05-05) Existing sidebar filtering supports requiredFeature, but route/API enforcement is not product-aware today.
  • (2026-05-05) server/src/components/layout/DefaultLayout.tsx imports full PSA cross-feature providers for workflows, scheduling, projects, assets, documents, msp-composition, and chat. AlgaDesk needs a separate shell/provider stack.
  • (2026-05-05) server/src/app/msp/layout.tsx registers SLA and schedule-entry integrations at module scope. This must move behind PSA-only registration or become product-aware.
  • (2026-05-05) packages/client-portal has broad barrels/actions/components that include billing/projects/documents/devices/appointments/notifications. AlgaDesk portal needs narrowed entrypoints/composition.
  • (2026-05-05) packages/tickets is relatively clean but still has hard/dynamic seams around email notifications and document uploads that need provider injection or narrowed exports.
  • (2026-05-05) Most v1 APIs share ApiBaseController; it is the main insertion point for product access after tenant resolution.
  • (2026-05-05) API metadata/OpenAPI must be filtered by product; otherwise AlgaDesk API clients will discover unusable PSA endpoints.

Commands / Runbooks

  • (2026-05-05) Design doc source: docs/plans/2026-05-05-algadesk-lightweight-helpdesk-product-seam-design.md.
  • (2026-05-05) Plan folder: ee/docs/plans/2026-05-05-algadesk-lightweight-helpdesk-product-seam/.
  • (2026-05-05) Validate JSON artifacts with python -m json.tool ee/docs/plans/2026-05-05-algadesk-lightweight-helpdesk-product-seam/features.json >/dev/null and same for tests.json.
  • Approved design doc: docs/plans/2026-05-05-algadesk-lightweight-helpdesk-product-seam-design.md
  • Existing tier constants: packages/types/src/constants/tenantTiers.ts
  • Existing tier feature mapping: packages/types/src/constants/tierFeatures.ts
  • Existing MSP layout: server/src/app/msp/layout.tsx
  • Existing layout/provider stack: server/src/components/layout/DefaultLayout.tsx
  • Existing menu config: server/src/config/menuConfig.ts
  • Existing API base controller: server/src/lib/api/controllers/ApiBaseController.ts
  • Existing ticket page: server/src/app/msp/tickets/page.tsx
  • Existing client portal package: packages/client-portal
  • Existing ticket package: packages/tickets

Open Questions

  • What exact branded copy and CTA should the upgrade boundary use?
  • Should AlgaDesk retain client/contact notes and interactions in v1?
  • Which inbound email providers/settings are required for launch versus later?
  • Should /desk/* aliases be added immediately after v1 or only when marketing requires them?
  • Should AlgaDesk have product-specific naming in app chrome or inherit existing Alga branding with AlgaDesk labels?

Implementation Log

  • (2026-05-05) Completed F001-F014 in one entitlement/access foundation slice.
  • Added shared product constants/types/resolution in packages/types/src/constants/productCodes.ts and exported via packages/types/src/constants/index.ts.
  • Added tenant interface support for product code in:
    • packages/types/src/interfaces/tenant.interface.ts
    • server/src/interfaces/tenant.interface.tsx
  • Added tenant schema migration server/migrations/20260505140000_add_tenant_product_code.cjs:
    • Adds tenants.product_code
    • Backfills NULL/empty values to psa
    • Adds CHECK constraint (psa|algadesk)
    • Sets NOT NULL + default psa
    • Down migration drops constraint + column
  • Added server product helpers and structured error in server/src/lib/productAccess.ts:
    • ProductAccessError with stable status=403 and code=PRODUCT_ACCESS_DENIED
    • getTenantProduct, getCurrentTenantProduct
    • assertProductAccess, assertTenantProductAccess
    • Unknown non-null product_code now fail-closed via ProductAccessError.

Tests Added

  • T001: server/src/test/integration/tenantProductCodeMigration.integration.test.ts
    • Verifies migration adds product_code, enforces default/not-null, allows algadesk, rejects invalid values.
  • T002: server/src/test/unit/productAccess.test.ts
    • Verifies resolver defaults, algadesk pass-through, fail-closed unknown values, and structured denial error behavior.
  • Additional type/unit coverage:
    • packages/types/src/constants/productCodes.test.ts
    • packages/types/src/interfaces/tenant.interface.typecheck.test.ts (added product_code assertion)

Commands Run

  • cd server && npx vitest run ../packages/types/src/constants/productCodes.test.ts ../packages/types/src/interfaces/tenant.interface.typecheck.test.ts src/test/unit/productAccess.test.ts
    • Result: pass (15 tests)
  • cd server && npx vitest run ../packages/types/src/constants/productCodes.test.ts ../packages/types/src/interfaces/tenant.interface.typecheck.test.ts src/test/unit/productAccess.test.ts src/test/integration/tenantProductCodeMigration.integration.test.ts
    • Result: integration suite could not run in this environment because Postgres was not reachable on localhost:5432 (ECONNREFUSED).

Gotchas

  • Repository test:local script currently passes an invalid flag to the installed dotenv CLI (-e ../.env.localtest), so direct vitest invocation was used.
  • DB-backed integration test is present and meaningful, but executing it requires a running test Postgres instance.
  • (2026-05-05) Completed F015 by introducing client-safe product context in server/src/context/ProductContext.tsx and wiring it into server/src/app/msp/MspLayoutClient.tsx.
  • Added unit coverage for product context resolution in server/src/test/unit/context/ProductContext.test.tsx.
  • (2026-05-05) Completed F016/F017/F018 compatibility/docs pass.
  • Added explicit orthogonality documentation comments in:
    • packages/types/src/constants/tenantTiers.ts
    • packages/types/src/constants/addOns.ts
    • server/src/lib/productAccess.ts
  • Added regression assertion in packages/types/src/constants/productCodes.test.ts that product entitlement work does not alter tier or add-on resolution behavior.
  • (2026-05-05) Completed F019-F034 and T003 with a pure product surface registry module: server/src/lib/productSurfaceRegistry.ts.
  • Registry now includes:
    • Product capability definitions (psa + algadesk)
    • MSP and client portal route-group behavior maps (allowed, upgrade_boundary, not_found)
    • API group behavior map (allowed|denied) and metadata visibility filtering
    • Static/dynamic matcher helpers
    • Path behavior resolvers for routes and APIs
    • Menu + portal navigation filtering helpers
    • Fail-closed unknown behavior for AlgaDesk
    • /desk/* alias normalization mapped to MSP route groups
  • Added server/src/test/unit/productSurfaceRegistry.test.ts to validate representative route/API classifications and fail-closed behavior.
  • (2026-05-05) Completed F035/F036/F037 by extending server/test-utils/testDataFactory.ts#createTenant with optional productCode input and default product_code: 'psa'.
  • Added/update contract test server/src/test/unit/testDataFactory.test.ts to enforce default + explicit AlgaDesk fixture support.
  • (2026-05-05) Completed F038 by threading optional product entitlement through provisioning flows:
    • Added productCode?: 'psa' | 'algadesk' to EE tenant creation interfaces in ee/server/src/interfaces/tenant.interfaces.ts and ee/temporal-workflows/src/types/workflow-types.ts.
    • Passed productCode through workflow activity invocation in ee/temporal-workflows/src/workflows/tenant-creation-workflow.ts.
    • Updated tenant creation DB write in ee/temporal-workflows/src/db/tenant-operations.ts to set tenantData.product_code when provided.
    • Updated provisioning surfaces to accept/set product entitlement:
      • ee/server/src/app/api/v1/tenant-management/create-tenant/route.ts
      • ee/server/src/services/provisioning/types/tenant.schema.ts
      • ee/server/src/services/provisioning/tenantService.ts
  • (2026-05-05) Completed F039/F040 by ensuring tier transitions do not write product entitlement:
    • Added regression assertions in ee/server/src/__tests__/unit/stripeService.tierPricing.test.ts that tenant update payloads do not include product_code.
    • Added focused IAP transition guard test ee/server/src/__tests__/unit/stripeService.productCodePreservation.test.ts.
  • (2026-05-05) Command run: cd ee/server && npx vitest run src/__tests__/unit/stripeService.tierPricing.test.ts src/__tests__/unit/stripeService.productCodePreservation.test.ts.
    • Result: failed before test execution in this environment due to module resolution (Cannot find package '@/lib/db/db') from ee/server Vitest context.
  • (2026-05-05) Completed F041 by exposing product_code in admin-only tenant listing response at ee/server/src/app/api/v1/tenant-management/tenants/route.ts.
  • (2026-05-05) Completed F042: existing down migration in server/migrations/20260505140000_add_tenant_product_code.cjs already safely drops product-code constraint then column when present.
  • (2026-05-05) Completed F043-F060 and T004 with new @alga-psa/algadesk-composition scaffold:
    • Added package scaffold and exports in:
      • packages/algadesk-composition/package.json
      • packages/algadesk-composition/project.json
      • packages/algadesk-composition/src/index.ts
    • Added focused composition entrypoints for MSP, portal, tickets, clients/contacts, settings, KB, and providers in:
      • packages/algadesk-composition/src/msp/index.ts
      • packages/algadesk-composition/src/portal/index.ts
      • packages/algadesk-composition/src/tickets/index.ts
      • packages/algadesk-composition/src/clients/index.ts
      • packages/algadesk-composition/src/settings/index.ts
      • packages/algadesk-composition/src/kb/index.ts
      • packages/algadesk-composition/src/providers/index.ts
    • Dependencies intentionally constrained to @alga-psa/types only.
    • Added static guard test server/src/test/unit/algadeskCompositionDependencyGuard.test.ts asserting:
      • Package exists with required exports.
      • Package dependencies exclude blocked domains (billing/projects/assets/scheduling/SLA/workflows/surveys/extensions/AI/reporting).
      • Source imports do not reference blocked package domains.
  • (2026-05-05) Command run: cd server && npx vitest run src/test/unit/algadeskCompositionDependencyGuard.test.ts.
    • Result: pass (2 tests).
  • (2026-05-05) Completed F061-F067 MSP layout seam increment:
    • server/src/app/msp/layout.tsx now resolves productCode via getCurrentTenantProduct() and passes it to the client layout.
    • Moved SLA/schedule integration registration out of module scope and gated registration to PSA-only (productCode === 'psa').
    • server/src/app/msp/MspLayoutClient.tsx now branches shell rendering by product: PSA keeps DefaultLayout; AlgaDesk uses a distinct shell path (data-product-shell="algadesk") without forcing full PSA layout providers.
  • (2026-05-05) Completed F068-F087 by product-filtering sidebar navigation through the registry helper:
    • Updated server/src/components/layout/SidebarWithFeatureFlags.tsx to apply filterMenuSectionsByProduct(productCode, ...) using ProductContext.
    • AlgaDesk now keeps only route-allowed main navigation entries from the existing menu config, which removes PSA-only areas (billing/projects/assets/schedule/time/workflows/surveys/extensions/service-requests, etc.) while keeping dashboard/tickets/clients/contacts/KB/settings/profile/security.
    • PSA tenant behavior remains unchanged (productCode: psa keeps full allowed menu set).
  • (2026-05-05) Command run: cd server && npx vitest run src/test/unit/productSurfaceRegistry.test.ts.
    • Result: pass (7 tests).
  • (2026-05-05) Completed F088 by making MSP shell branding product-aware without changing PSA shell behavior:
    • server/src/components/layout/Sidebar.tsx now accepts appDisplayName and appLogoAlt props (defaults preserve PSA).
    • server/src/components/layout/SidebarWithFeatureFlags.tsx sets AlgaDesk branding labels when productCode === 'algadesk'.
    • server/src/app/msp/MspLayoutClient.tsx uses product-aware client UI shell title (AlgaDesk MSP vs MSP Portal).
  • (2026-05-05) Completed T005 with component coverage in server/src/test/unit/layout/SidebarWithFeatureFlags.productShell.test.tsx:
    • Asserts AlgaDesk shell filters out blocked modules and uses AlgaDesk branding labels.
    • Asserts PSA shell still includes representative PSA modules and uses AlgaPSA branding labels.
  • (2026-05-05) Command run: cd server && npx vitest run src/test/unit/layout/SidebarWithFeatureFlags.productShell.test.tsx.
    • Result: pass (2 tests).
  • (2026-05-05) Completed F089-F102 and T007 with an AlgaDesk-specific dashboard composition.
  • Added dashboard data action server/src/lib/actions/algadeskDashboardActions.ts with tenant-scoped summaries for:
    • open ticket count
    • awaiting customer / awaiting internal counts
    • ticket aging buckets (<2d, 2-7d, >7d)
    • recently updated tickets
    • email channel health summary from email_providers
  • Added AlgaDesk dashboard UI server/src/components/dashboard/AlgaDeskDashboard.tsx with helpdesk-only cards/sections and no PSA-only widgets.
  • Updated server/src/app/msp/dashboard/page.tsx to resolve tenant product and render AlgaDesk dashboard for algadesk while preserving existing PSA dashboard behavior.
  • (2026-05-05) Completed F103-F109 and F111-F123 plus T006 with AlgaDesk settings tab composition narrowing.
  • Added product-aware settings tab allowlist helper: server/src/lib/settingsProductTabs.ts.
  • Updated server/src/components/settings/SettingsPage.tsx to:
    • resolve current product via useProduct()
    • filter available tabs to AlgaDesk-approved scope (general/users/teams/ticketing/email/client-portal)
    • fail closed to general when excluded tab query params are requested.
  • Updated sidebar settings mode filtering:
    • server/src/components/layout/Sidebar.tsx accepts settingsSectionsOverride.
    • server/src/components/layout/SidebarWithFeatureFlags.tsx passes product-filtered settings sections.
  • Updated registry behavior in server/src/lib/productSurfaceRegistry.ts:
    • added explicit AlgaDesk not_found route behavior for /msp/settings/sla
    • added AlgaDesk query-tab filtering for /msp/settings?tab=... links in menu filtering.

Tests Added

  • T006: server/src/test/unit/settings/settingsProductTabs.test.ts
    • Asserts AlgaDesk-approved settings tabs are present and excluded tabs (billing/SLA/projects/time-entry/integrations/extensions/experimental) are not allowed.
  • T007: server/src/test/unit/dashboard/AlgaDeskDashboard.contract.test.ts
    • Asserts AlgaDesk dashboard contains ticket/email summary sections and excludes PSA-only widget labels.

Commands Run

  • cd server && npx vitest run src/test/unit/dashboard/AlgaDeskDashboard.contract.test.ts src/test/unit/settings/settingsProductTabs.test.ts src/test/unit/layout/SidebarWithFeatureFlags.productShell.test.tsx src/test/unit/productSurfaceRegistry.test.ts
    • Result: pass (13 tests).
  • (2026-05-05) Completed F110 by adding an explicit Knowledge Base settings entry for AlgaDesk.
  • Added knowledge-base settings tab in server/src/components/settings/SettingsPage.tsx with focused KB management handoff link to /msp/knowledge-base.
  • Added settings nav item in server/src/config/menuConfig.ts and extended product allowlists for knowledge-base in:
    • server/src/lib/settingsProductTabs.ts
    • server/src/lib/productSurfaceRegistry.ts
  • Updated T006 assertion coverage to include AlgaDesk KB tab allowlist expectation.

Commands Run (additional)

  • cd server && npx vitest run src/test/unit/settings/settingsProductTabs.test.ts src/test/unit/productSurfaceRegistry.test.ts src/test/unit/layout/SidebarWithFeatureFlags.productShell.test.tsx
    • Result: pass (11 tests).
  • (2026-05-05) Completed F124-F140 and T008 with product-aware ticket list composition.
  • Added AlgaDesk-safe SLA filter seam in ticket list stack:
    • server/src/app/msp/tickets/page.tsx
    • packages/tickets/src/components/TicketingDashboardContainer.tsx
    • packages/tickets/src/components/TicketingDashboard.tsx
  • Behavior:
    • AlgaDesk tenants now render ticket list with allowSlaStatusFilter=false and URL slaStatusFilter is ignored.
    • PSA tenants keep existing SLA filter behavior.
    • Core ticket list filters (board/status/priority/category/client/search/tags/assignee/team/unassigned/due-date/response-state), sorting, and pagination remain unchanged.
  • Added test coverage:
    • server/src/test/unit/app/msp/tickets/page.productComposition.test.tsx
    • Asserts AlgaDesk disables SLA filter composition while PSA preserves it.

Commands Run (additional)

  • cd server && npx vitest run src/test/unit/app/msp/tickets/page.productComposition.test.tsx src/test/unit/productSurfaceRegistry.test.ts src/test/unit/layout/SidebarWithFeatureFlags.productShell.test.tsx
    • Result: pass (11 tests).
  • (2026-05-05) Completed F141 by validation of existing bulk move constraints.
  • Current ticket bulk move path already only accepts destinationBoardId + destinationStatusId (packages/tickets/src/components/TicketingDashboard.tsx, packages/tickets/src/actions/ticketActions.ts), with board/status validation server-side and no bulk move hooks for excluded PSA operations.
  • (2026-05-05) Completed F142/F144/F145 with product-aware ticket detail composition routing.
  • Updated server/src/app/msp/tickets/[id]/page.tsx to resolve tenant product and branch details mode:
    • AlgaDesk path disables survey fetch, omits associated assets panel, bypasses AI chat boundary, and passes isAlgaDeskMode=true.
    • PSA path preserves existing behavior.
  • Updated packages/msp-composition/src/tickets/MspTicketDetailsContainerClient.tsx with isAlgaDeskMode:
    • Omits project task create/link/badge composition hooks in AlgaDesk mode (F167/F168/F169).
    • Omits interval/time management composition hooks in AlgaDesk mode (F170/F171).
    • Omits survey summary card in AlgaDesk mode (F173).
  • Combined with detail-page associated-assets omission and AI boundary bypass:
    • Omits associated assets panel in AlgaDesk mode (F172).
    • Removes AI detail context wrapper in AlgaDesk mode (F174).

Tests Added

  • server/src/test/unit/app/msp/tickets/[id]/page.productComposition.test.tsx
    • Asserts AlgaDesk detail path sets isAlgaDeskMode=true and omits associated assets.
    • Asserts PSA detail path remains in standard mode and keeps survey summary fetch behavior.

Commands Run (additional)

  • cd server && npx vitest run src/test/unit/app/msp/tickets/page.productComposition.test.tsx 'src/test/unit/app/msp/tickets/[id]/page.productComposition.test.tsx'
    • Result: pass (4 tests).
  • (2026-05-05) Completed F143 with explicit AlgaDesk quick-add form composition wiring from MSP ticket list.
  • Added product-aware quick-add flag flow:
    • server/src/app/msp/tickets/page.tsx now passes useAlgaDeskQuickAddForm based on tenant product.
    • packages/tickets/src/components/TicketingDashboardContainer.tsx and packages/tickets/src/components/TicketingDashboard.tsx thread the flag into QuickAddTicket.
    • packages/tickets/src/components/QuickAddTicket.tsx now supports isAlgaDeskMode and fail-closes asset-prefill linkage in AlgaDesk mode (asset_id not submitted, asset banner hidden).
  • Updated test server/src/test/unit/app/msp/tickets/page.productComposition.test.tsx to assert AlgaDesk receives quick-add safe composition flag while PSA does not.

Commands Run (additional)

  • cd server && npx vitest run src/test/unit/app/msp/tickets/page.productComposition.test.tsx 'src/test/unit/app/msp/tickets/[id]/page.productComposition.test.tsx'
    • Result: pass (4 tests).
  • (2026-05-05) Completed F165/F166 by removing SLA status and SLA breach badges from AlgaDesk ticket detail.
  • Added hideSlaStatus composition seam threaded through ticket detail stack:
    • packages/tickets/src/components/ticket/TicketInfo.tsx
    • packages/tickets/src/components/ticket/TicketDetails.tsx
    • packages/tickets/src/components/ticket/TicketDetailsContainer.tsx
    • packages/msp-composition/src/tickets/MspTicketDetailsContainerClient.tsx (sets hideSlaStatus when isAlgaDeskMode=true).
  • Behavior:
    • AlgaDesk ticket detail now hides SLA status block and SLA breach indicators.
    • PSA ticket detail retains existing SLA rendering behavior.
  • Validation:
    • src/test/unit/app/msp/tickets/[id]/page.productComposition.test.tsx still passes and confirms AlgaDesk detail composition mode.
    • A direct package-level TicketDetailsContainer test run is currently blocked in this environment by a pre-existing module-resolution issue (next-auth ./lib/env specifier) under this Vitest context.
  • (2026-05-05) Completed F146-F164 and F175; validated AlgaDesk ticket detail core composition remains intact while PSA-only controls stay excluded.
  • Added/expanded contract coverage in packages/msp-composition/src/tickets/__tests__/MspTicketDetailsContainerClient.test.tsx:
    • New AlgaDesk-focused test (T009) asserts core ticket detail payload wiring remains present (metadata options, conversation/comments, documents/attachments, client/contact context, assignment/reference options) while SLA/project/time controls are omitted.
    • Added PSA default-mode assertion that project/time and SLA composition hooks remain available for PSA tenants.

Commands Run (additional)

  • cd server && npx vitest run ../packages/msp-composition/src/tickets/__tests__/MspTicketDetailsContainerClient.test.tsx src/test/unit/app/msp/tickets/page.productComposition.test.tsx 'src/test/unit/app/msp/tickets/[id]/page.productComposition.test.tsx'
    • Result: pending (run after this scratchpad update).
  • (2026-05-05) Adjusted T009 coverage location: package-level msp-composition test execution is currently blocked in this environment by existing cross-package module resolution (@alga-psa/authorization/kernel through tickets action graph), so T009 assertions were added to the existing server page-composition unit test instead.
  • Updated server/src/test/unit/app/msp/tickets/[id]/page.productComposition.test.tsx AlgaDesk assertions to verify core ticket detail data remains wired: comments/conversation payload, documents/attachments payload, status/priority/category options, and client/contact context, while retaining AlgaDesk PSA-surface omissions already covered (no associated assets, no survey fetch).

Commands Run (additional)

  • cd server && npx vitest run src/test/unit/app/msp/tickets/page.productComposition.test.tsx 'src/test/unit/app/msp/tickets/[id]/page.productComposition.test.tsx'
    • Result: pass (4 tests).
  • (2026-05-05) Completed F176-F189 quick-add/reference-data seam verification based on existing QuickAddTicket behavior plus targeted AlgaDesk-safe asset prefill contract coverage.
  • Added quick-add test case in packages/tickets/src/components/__tests__/ticket-inline-add-prefill.test.tsx:
    • T010 asserts PSA mode shows linked-asset banner and submits asset_id, while AlgaDesk mode hides banner and omits asset_id from ticket create payload.
  • Environment constraint: direct execution of package-level quick-add test suite remains blocked in this Vitest context by pre-existing next-auth deep import resolution (Missing "./lib/env" specifier in "next-auth" package).
  • (2026-05-05) Completed F190-F193 by verification: existing shared MSP clients/contacts list+detail routes already render for AlgaDesk tenants and are explicitly allowlisted in productSurfaceRegistry (/msp/clients, /msp/contacts). No additional composition fork required for these four baseline renderability items.
  • (2026-05-05) Completed client-detail exclusion slice for AlgaDesk: F202/F203/F204/F205/F207/F209/F210.
  • server/src/app/msp/clients/[id]/page.tsx now resolves tenant product and passes isAlgaDeskMode into ClientDetails; AlgaDesk path skips document fetch and survey-summary fetch.
  • packages/clients/src/components/clients/ClientDetails.tsx now filters tabs in AlgaDesk mode to remove PSA-only client surfaces (assets, billing, billing-dashboard, documents, tax-settings) and suppresses survey summary card rendering.
  • Added unit coverage: server/src/test/unit/app/msp/clients/[id]/page.productComposition.test.tsx verifying AlgaDesk vs PSA composition behavior (document/survey fetch suppression and mode flag propagation).
  • (2026-05-05) Completed F194/F195/F196/F197/F198/F199/F200/F201 and T012 with a client/contact composition safety pass for AlgaDesk.
  • Product-aware contact detail composition:
    • Updated server/src/app/msp/contacts/[id]/page.tsx to resolve tenant product and pass isAlgaDeskMode into contact detail.
    • AlgaDesk path now skips document fetch even on direct ?tab=documents requests.
  • Updated packages/clients/src/components/contacts/ContactDetails.tsx with isAlgaDeskMode prop and AlgaDesk tab filtering that removes the documents tab while preserving details/tickets/notes and existing CRUD/contact-edit surfaces.
  • Added unit coverage:
    • server/src/test/unit/app/msp/contacts/[id]/page.productComposition.test.tsx verifies AlgaDesk vs PSA contact detail behavior (document fetch suppressed for AlgaDesk, preserved for PSA).
    • server/src/test/unit/clients/algadeskClientContactComposition.contract.test.ts verifies client/contact CRUD + support context wiring remains intact for AlgaDesk composition (client/contact tickets, client locations, contact phone numbers, additional emails, notes), and confirms contact detail page product-safe mode wiring.

Commands Run (additional)

  • cd server && npx vitest run 'src/test/unit/app/msp/clients/[id]/page.productComposition.test.tsx' 'src/test/unit/app/msp/contacts/[id]/page.productComposition.test.tsx' src/test/unit/clients/algadeskClientContactComposition.contract.test.ts
    • Result: pass (7 tests).
    • Note: test run emits existing Next request-scope warnings from getServerTranslation cookie detection in this unit context; assertions still pass.
  • (2026-05-05) Completed F206/F208/F211 with tighter AlgaDesk-safe client detail composition gating.
  • Updated packages/clients/src/components/clients/ClientDetails.tsx:
    • Added shouldRenderPsaOnlyClientSurfaces guard to avoid invoking excluded cross-feature callbacks in AlgaDesk mode.
    • Guarded excluded tab callback execution for client assets and client documents so AlgaDesk mode does not execute those callbacks before tab filtering.
    • Expanded AlgaDesk excluded tab set to fail-closed for client projects and service catalog aliases (service-catalog, services) in addition to existing excluded domains.
  • Updated static contract coverage in server/src/test/unit/clients/algadeskClientContactComposition.contract.test.ts to assert:
    • explicit exclusion tokens for projects/service catalog
    • PSA-only callback guard presence in client detail composition.

Commands Run (additional)

  • cd server && npx vitest run src/test/unit/clients/algadeskClientContactComposition.contract.test.ts 'src/test/unit/app/msp/clients/[id]/page.productComposition.test.tsx'
    • Result: pass (5 tests).
  • (2026-05-05) Completed F212-F218 and T013 with KB composition/API contract coverage.
  • Added server/src/test/unit/kb/algadeskKnowledgeBase.contract.test.ts:
    • Verifies MSP and portal KB page compositions are wired (/msp/knowledge-base, /client-portal/knowledge-base).
    • Verifies KB list/view/create/edit/publish surfaces are exposed via API route handlers (kb-articles list/item/publish/archive routes).
    • Verifies KB UI components do not link to full document-library routes (/msp/documents, /client-portal/documents).
  • Scope note: this is confidence-building contract coverage (static/route-surface) consistent with plan non-goal of exhaustive per-feature integration.

Commands Run (additional)

  • cd server && npx vitest run src/test/unit/kb/algadeskKnowledgeBase.contract.test.ts
    • Result: pass (3 tests).
  • (2026-05-05) Completed F219 by introducing a composed ticket-attachment upload provider seam for ticket rich-text flows.
  • Added composed upload provider in packages/msp-composition/src/tickets/composedClipboardActions.ts:
    • uploadTicketAttachmentDocument(formData, { userId, ticketId }) delegates to documents upload action from composition layer.
  • Threaded upload provider through ticket detail composition chain:
    • packages/msp-composition/src/tickets/MspTicketDetailsContainerClient.tsx
    • packages/tickets/src/components/ticket/TicketDetailsContainer.tsx
    • packages/tickets/src/components/ticket/TicketDetails.tsx
    • packages/tickets/src/components/ticket/TicketInfo.tsx
    • packages/tickets/src/components/ticket/TicketConversation.tsx
  • Result: ticket description/comment clipboard image uploads can be provided by composition injection rather than relying on ticket components to directly resolve document upload actions.
  • (2026-05-05) Completed F220 by introducing a composed ticket-attachment draft-delete provider seam.
  • Threaded a delete-provider callback through ticket detail composition (TicketDetailsContainer -> TicketDetails -> TicketInfo/TicketConversation) and wired deleteDraftClipboardImages from msp-composition.
  • Result: AlgaDesk ticket clipboard-draft cleanup now uses composition-provided delete action wiring instead of relying only on local fallback behavior.

Commands Run (additional)

  • cd server && npx vitest run ../packages/tickets/src/components/ticket/useTicketRichTextUploadSession.test.tsx src/test/unit/app/msp/tickets/page.productComposition.test.tsx 'src/test/unit/app/msp/tickets/[id]/page.productComposition.test.tsx'
    • Result: pass (9 tests).
  • cd server && npx vitest run ../packages/msp-composition/src/tickets/__tests__/MspTicketDetailsContainerClient.test.tsx src/test/unit/app/msp/tickets/page.productComposition.test.tsx 'src/test/unit/app/msp/tickets/[id]/page.productComposition.test.tsx'
    • Result: package test blocked by existing Vitest mock/export mismatch in this environment (@alga-psa/auth mock missing withAuthCheck); server page-composition tests still pass.
  • (2026-05-05) Completed F221 by introducing a composed ticket-attachment view/download URL resolver seam.
  • Added resolveTicketAttachmentViewUrl in packages/msp-composition/src/tickets/composedClipboardActions.ts and threaded it through ticket detail composition (TicketDetailsContainer -> TicketDetails -> TicketInfo/TicketConversation) into useTicketRichTextUploadSession.
  • useTicketRichTextUploadSession now accepts resolveDocumentViewUrl so AlgaDesk ticket components can resolve attachment URLs via composition rather than hardcoded URL construction in component logic.
  • Added hook-level coverage in packages/tickets/src/components/ticket/useTicketRichTextUploadSession.test.tsx to assert injected URL-resolver behavior.
  • (2026-05-05) Completed F222 by disabling folder selection for AlgaDesk ticket attachment uploads.
  • Added composition flag disableAttachmentFolderSelection (set to isAlgaDeskMode) through ticket detail composition to ticket documents section.
  • Added forceUploadToRoot support in documents component wiring so entity-mode uploads bypass folder chooser when this flag is enabled.
  • Added contract coverage: server/src/test/unit/tickets/algadeskAttachmentComposition.contract.test.ts.
  • Command run: cd server && npx vitest run src/test/unit/tickets/algadeskAttachmentComposition.contract.test.ts ../packages/tickets/src/components/ticket/useTicketRichTextUploadSession.test.tsx src/test/unit/app/msp/tickets/page.productComposition.test.tsx 'src/test/unit/app/msp/tickets/[id]/page.productComposition.test.tsx' -> pass (12 tests).
  • (2026-05-05) Completed F223/F224 by gating AlgaDesk ticket attachment surfaces away from broad document management affordances.
  • Added documents component controls:
    • allowDocumentSharing to suppress share-link dialog/actions.
    • allowLinkExistingDocuments to suppress "link existing documents" picker in entity mode.
  • Threaded AlgaDesk-safe flags through ticket detail composition and ticket documents section:
    • disableAttachmentSharing / disableAttachmentLinking set from isAlgaDeskMode in MspTicketDetailsContainerClient.
  • Updated static contract assertions in server/src/test/unit/tickets/algadeskAttachmentComposition.contract.test.ts.
  • Command run: cd server && npx vitest run src/test/unit/tickets/algadeskAttachmentComposition.contract.test.ts ../packages/tickets/src/components/ticket/useTicketRichTextUploadSession.test.tsx src/test/unit/app/msp/tickets/page.productComposition.test.tsx 'src/test/unit/app/msp/tickets/[id]/page.productComposition.test.tsx' -> pass (12 tests).
  • (2026-05-05) Completed F225 by asserting rich-text image uploads remain ticket/comment scoped.
  • Expanded useTicketRichTextUploadSession test coverage to verify uploads are invoked with explicit { userId, ticketId } metadata.
  • Command run: cd server && npx vitest run ../packages/tickets/src/components/ticket/useTicketRichTextUploadSession.test.tsx src/test/unit/tickets/algadeskAttachmentComposition.contract.test.ts -> pass (8 tests).
  • (2026-05-05) Completed F226 by strengthening/validating ticket-attachment authorization contract coverage.
  • Updated packages/tickets/src/actions/comment-actions/clipboardImageDraftActions.contract.test.ts assertions to match current injected delete implementation (input.deleteDocumentFn(...)) and explicit ticket-association check.
  • Command run: cd server && npx vitest run ../packages/tickets/src/actions/comment-actions/clipboardImageDraftActions.contract.test.ts ../packages/tickets/src/components/ticket/useTicketRichTextUploadSession.test.tsx -> pass (10 tests).
  • (2026-05-05) Completed F227-F243 with product-aware client-portal shell composition.
  • Server/client layout threading:
    • server/src/app/client-portal/layout.tsx now resolves tenant product via getCurrentTenantProduct() and passes productCode into ClientPortalLayoutClient.
    • server/src/app/client-portal/ClientPortalLayoutClient.tsx now passes productCode to ClientPortalLayout.
  • Client portal shell updates:
    • packages/client-portal/src/components/layout/ClientPortalLayout.tsx now threads productCode to sidebar and marks shell root with data-product-shell.
    • packages/client-portal/src/components/layout/ClientPortalSidebar.tsx now applies AlgaDesk-specific nav composition:
      • keeps dashboard/tickets/knowledge-base/profile and client settings (when permitted)
      • hides billing/projects/devices/documents/appointments/request-services/extensions
      • keeps PSA behavior unchanged by default (productCode='psa').
  • Added/updated contract coverage:
    • server/src/test/unit/client-portal/clientPortalProductLayout.contract.test.ts
    • packages/client-portal/src/components/layout/ClientPortalSidebar.contract.test.ts
  • Command run: cd server && npx vitest run src/test/unit/client-portal/clientPortalProductLayout.contract.test.ts ../packages/client-portal/src/components/layout/ClientPortalSidebar.contract.test.ts -> pass (8 tests).
  • (2026-05-05) Completed F244-F261 and T014 via client-portal ticketing/shell contract verification.
  • Added server/src/test/unit/client-portal/algadeskPortalTicketing.contract.test.ts to validate:
    • dashboard/ticket list/detail surfaces remain wired
    • free-form ticket creation inputs and visibility-group board enforcement
    • portal ticket detail hides internal-comment tab and keeps public reply/status flows
    • portal ticket pages avoid billing/project navigation links
  • Command run: cd server && npx vitest run src/test/unit/client-portal/algadeskPortalTicketing.contract.test.ts src/test/unit/client-portal/clientPortalProductLayout.contract.test.ts -> pass (8 tests).
  • (2026-05-05) Completed F262 and T021 with focused AlgaDesk Email Channels settings composition.
  • Updated server/src/components/settings/SettingsPage.tsx email tab rendering:
    • AlgaDesk now uses EmailProviderConfiguration (inbound mailbox/channel-focused settings surface).
    • PSA retains existing EmailSettings composition unchanged.
  • Added server/src/test/unit/settings/algadeskEmailChannelsComposition.contract.test.ts to assert product-aware composition switch in settings.
  • (2026-05-05) Completed F263 and T022 by validating existing inbound mailbox/channel configuration seams.
  • Added server/src/test/unit/settings/algadeskInboundEmailChannelConfiguration.contract.test.ts asserting:
    • inbound provider setup UI covers Gmail/Microsoft/IMAP mailbox channel configuration
    • provider persistence contract includes provider_type, mailbox, is_active, and inbound_ticket_defaults_id
  • Command run: cd server && npx vitest run src/test/unit/settings/algadeskInboundEmailChannelConfiguration.contract.test.ts src/test/unit/settings/algadeskEmailChannelsComposition.contract.test.ts -> pass (2 tests).
  • Note: legacy EmailProviderConfiguration component tests currently fail in this environment due to pre-existing DB/mocking issues (ECONNREFUSED + missing mock export for getInboundTicketDefaults); product-seam contracts were added to keep this plan slice verifiable without DB coupling.
  • (2026-05-05) Completed F264-F271 and T023 using existing email-channel form/status seams plus contract coverage.
  • Added server/src/test/unit/settings/algadeskEmailChannelMappingsAndHealth.contract.test.ts validating:
    • outbound identity seam persists mailbox-based channel identity
    • inbound defaults form includes board/category/priority mapping + active toggle
    • provider card exposes connection status, last-sync health, webhook-expiry context, and last error state
  • Command run: cd server && npx vitest run src/test/unit/settings/algadeskEmailChannelsComposition.contract.test.ts src/test/unit/settings/algadeskInboundEmailChannelConfiguration.contract.test.ts src/test/unit/settings/algadeskEmailChannelMappingsAndHealth.contract.test.ts -> pass (3 tests).
  • (2026-05-06) Completed F272-F280 by validating existing unified inbound email processing implementation already satisfies the feature slice.
  • Implementation evidence in shared/services/email/processInboundEmailInApp.ts:
    • Creates new tickets from inbound email and maps destination defaults (board_id, category_id, priority_id) via resolveEffectiveInboundTicketDefaults + createTicketFromEmail (F272/F273/F274/F275).
    • Resolves sender contact when possible and applies fallback behavior for unknown senders (domain/client fallback + unmatched sender metadata) (F276/F277).
    • Adds public comments for inbound replies through reply-token/thread-header matching via createCommentFromEmail (F278).
    • Persists message/thread identifiers in ticket/comment metadata and uses them for thread lookup/idempotency (F279).
    • Dedupes repeated inbound events by pre-create checks for existing ticket/comment message IDs (F280).
  • Coverage references already present in shared tests:
    • shared/services/email/__tests__/processInboundEmailInApp.test.ts
    • shared/services/email/__tests__/processInboundEmailInApp.additionalPaths.test.ts
    • server/src/test/unit/unifiedInboundEmailQueueJobProcessor.fetch.test.ts
  • Command run: cd server && npx vitest run src/test/unit/unifiedInboundEmailQueueJobProcessor.fetch.test.ts -> pass (8 tests).
  • Note: shared inbound-email unit tests live outside the current server Vitest include globs in this environment, so direct invocation from root reports No test files found; existing shared test files remain the primary coverage artifact for this slice.
  • (2026-05-06) Completed F281 and F282 by validating existing outbound-notification and ticket-detail email-context seams.
  • Implementation evidence:
    • server/src/lib/eventBus/subscribers/ticketEmailSubscriber.ts sends external-contact outbound email when a comment is public and from an internal agent (isPublicComment && isFromAgent) and routes through sendNotificationIfEnabled (feature-flag/preference-aware) (F281).
    • packages/tickets/src/components/ticket/TicketDetails.tsx renders ticket email context composition (TicketEmailNotifications) and inbound-origin metadata labels; detail context surfaces are already wired in MSP ticket detail composition tests (F282).
  • (2026-05-06) Completed F283/F284/F285 with explicit product gating on inbound email webhook, IMAP management, and OAuth routes.
  • Added product assertions on server email routes:
    • server/src/app/api/email/oauth/initiate/route.ts
    • server/src/app/api/email/oauth/imap/initiate/route.ts
    • server/src/app/api/email/oauth/imap/callback/route.ts
    • server/src/app/api/email/imap/resync/route.ts
    • server/src/app/api/email/imap/reconnect/route.ts
    • Each now calls assertTenantProductAccess(... capability: 'email_to_ticket', allowedProducts: ['psa', 'algadesk']).
  • Added tenant product checks to webhook handlers:
    • packages/integrations/src/webhooks/email/handlers/googleWebhookHandler.ts
    • packages/integrations/src/webhooks/email/handlers/microsoftWebhookHandler.ts
    • packages/integrations/src/webhooks/email/imap.ts
    • Handlers now fail closed for unknown tenant product_code values before enqueueing inbound pointer jobs.
  • Added contract coverage:
    • server/src/test/unit/email/algadeskEmailRouteProductGate.contract.test.ts
  • Commands run:
    • cd server && npx vitest run src/test/unit/email/algadeskEmailRouteProductGate.contract.test.ts -> pass (1 test).
    • cd server && npx vitest run src/test/unit/tickets/TicketDetails.emailNotifications.integration.test.ts src/test/unit/notifications/ticketEmailSubscriber.watchers.test.ts -> pass (8 tests).
  • (2026-05-06) Completed F286 by verification of existing settings-product filtering:
    • server/src/lib/settingsProductTabs.ts excludes broad integrations tab for AlgaDesk.
    • server/src/components/settings/SettingsPage.tsx applies product-tab allowlist and keeps focused email tab for channel configuration.
  • (2026-05-06) Completed F287/F288 by product-gating notifications email-template surfaces for AlgaDesk.
    • Updated server/src/components/settings/general/NotificationsTab.tsx:
      • Added product-aware email tab allowlist for AlgaDesk (settings, categories, telemetry).
      • Removed email-templates tab from AlgaDesk composition while preserving PSA behavior.
      • Sanitized direct section=email-templates query access by restricting valid tabs in AlgaDesk mode.
    • Updated server/src/app/msp/settings/notifications/page.tsx:
      • Added product-aware email tab allowlist for AlgaDesk (settings, categories).
      • Removed email-templates tab from direct notifications route in AlgaDesk mode while preserving PSA behavior.
      • Sanitized direct tab=email-templates query access by restricting valid tabs in AlgaDesk mode.
    • Added contract test T024: server/src/test/unit/settings/algadeskEmailTemplateAutomationBoundary.contract.test.ts asserting AlgaDesk-specific tab gating in both compositions.

Commands Run (additional)

  • cd server && npx vitest run src/test/unit/settings/algadeskEmailTemplateAutomationBoundary.contract.test.ts src/test/unit/settings/algadeskEmailChannelsComposition.contract.test.ts src/test/unit/settings/algadeskInboundEmailChannelConfiguration.contract.test.ts

    • Result: pass (4 tests).
  • (2026-05-06) Completed F289-F307 and T025 via shared route-boundary composition in layout clients.

    • Added reusable branded boundary component:
      • server/src/components/product/ProductRouteBoundary.tsx
      • Handles both upgrade_boundary and not_found registry outcomes with scoped dashboard return links for MSP and client portal.
    • Wired registry-backed gating in MSP layout:
      • server/src/app/msp/MspLayoutClient.tsx
      • Resolves resolveProductRouteBehavior(productCode, pathname) and renders ProductRouteBoundary for non-allowed AlgaDesk routes.
    • Wired registry-backed gating in client portal layout:
      • server/src/app/client-portal/ClientPortalLayoutClient.tsx
      • Resolves resolveProductRouteBehavior(productCode, pathname) and renders ProductRouteBoundary for non-allowed AlgaDesk routes.
    • Effect:
      • AlgaDesk direct hits to excluded MSP major routes (billing/projects/assets/schedule/dispatch/time/workflows/surveys/extensions/reports/service-requests) now consistently render an upgrade boundary.
      • AlgaDesk direct hits to internal/test MSP route groups (/msp/test*) resolve to product not-available boundary.
      • AlgaDesk direct hits to excluded portal routes (billing/projects/devices/documents/appointments/request-services/extensions) now render portal boundary.
      • PSA behavior remains unchanged because layout gating applies only when productCode === 'algadesk'.
    • Added contract test server/src/test/unit/product/productRouteBoundaryComposition.contract.test.ts.
  • cd server && npx vitest run src/test/unit/product/productRouteBoundaryComposition.contract.test.ts src/test/unit/productSurfaceRegistry.test.ts

    • Result: pass (10 tests).
  • (2026-05-06) Completed F308-F332 and F336 with centralized API controller product gating.

    • Updated server/src/lib/api/controllers/ApiBaseController.ts:
      • Added assertProductApiAccess() invoked immediately after authentication in list/get/create/update/delete handlers.
      • Resolves tenant product via getTenantProduct(...) and API behavior via resolveProductApiBehavior(...).
      • Added structured ProductDeniedApiError (statusCode: 403, code: PRODUCT_ACCESS_DENIED) for denied AlgaDesk API groups.
    • Result: API-key paths that use ApiBaseController now enforce product boundary centrally while preserving existing PSA behavior (psa routes continue as allowed by registry).
    • Added contract test T026:
      • server/src/test/unit/api/apiBaseController.productAccess.contract.test.ts.
  • cd server && npx vitest run src/test/unit/api/apiBaseController.productAccess.contract.test.ts src/test/unit/productSurfaceRegistry.test.ts

    • Result: pass (8 tests).
  • (2026-05-06) Completed F333/F334/F335/F354 and T027 via product-aware metadata/OpenAPI filtering.

    • Updated server/src/lib/api/controllers/ApiMetadataController.ts:
      • Applies assertProductApiAccess across metadata controller endpoints (same guard model as ApiBaseController flow).
      • Filters /api/v1/meta/endpoints response to only product-visible endpoint paths using isApiVisibleInMetadata(productCode, endpoint.path).
      • Filters /api/v1/meta/openapi paths object to only product-visible paths for current tenant product.
    • PSA preservation: visibility filter returns full metadata for psa rules, preserving existing PSA metadata output.
    • Added contract test:
      • server/src/test/unit/api/apiMetadataController.productVisibility.contract.test.ts.
  • cd server && npx vitest run src/test/unit/api/apiMetadataController.productVisibility.contract.test.ts src/test/unit/api/apiBaseController.productAccess.contract.test.ts src/test/unit/productSurfaceRegistry.test.ts

    • Result: pass (9 tests).
  • (2026-05-06) Completed F337/F338/F339/F340/F341/F342/F345/F346/F347/F348 and alias completion F355/F356 with representative server-action product assertions.

  • Added shared guard shared/services/productAccessGuard.ts:

    • assertPsaOnlyTenantAccess(tenantId, capability) resolves tenant product_code from admin DB and fail-closes non-PSA or misconfigured tenants.
    • Includes structured ProductAccessError (status=403, code=PRODUCT_ACCESS_DENIED).
  • Added PSA-only guard calls in representative excluded-domain server actions:

    • Billing: packages/billing/src/actions/taxRateActions.ts
    • Projects: packages/projects/src/actions/projectTaskExportActions.ts
    • Scheduling/time: packages/scheduling/src/actions/timeEntryTicketActions.ts
    • Assets/RMM: packages/assets/src/actions/clientLookupActions.ts
    • Workflow: server/src/lib/actions/workflow-bundle-v1-actions.ts
    • Surveys: packages/surveys/src/actions/survey-actions/surveyMetricsActions.ts
    • Full document-management: packages/documents/src/actions/shareLinkActions.ts
  • Added contract coverage server/src/test/unit/product/serverActionProductAccess.contract.test.ts to assert representative guards remain present and guard uses structured ProductAccessError fields.

  • F346 rationale: existing algadesk-composition dependency guard already blocks imports of excluded domains (including extensions) from AlgaDesk composition package, satisfying “avoid importing denied server action modules from AlgaDesk composition” at package-boundary level.

  • F355/F356 rationale: route registry already maps optional /desk/* aliases without requiring aliases for launch via normalizePathname in server/src/lib/productSurfaceRegistry.ts.

Commands Run (additional)

  • cd server && npx vitest run src/test/unit/product/serverActionProductAccess.contract.test.ts src/test/unit/productAccess.test.ts
    • Result: pass (9 tests).
  • (2026-05-06) Completed F343/F344 with extension and AI/chat boundary assertions.
  • Added extension product gates (extension_actions) in EE action surfaces:
    • ee/server/src/lib/actions/extMenuActions.ts
    • ee/server/src/lib/actions/extensionDomainActions.ts
    • ee/server/src/lib/actions/installDomainActions.ts
  • Added AI/chat product gates (ai_chat) in representative chat server endpoints:
    • server/src/app/api/chat/v1/execute/route.ts
    • server/src/app/api/chat/v1/completions/route.ts
    • server/src/app/api/chat/v1/completions/stream/route.ts
    • Each now requires session tenant and asserts PSA-only product access.
  • Updated product access contract test to include extension and AI/chat representative files:
    • server/src/test/unit/product/serverActionProductAccess.contract.test.ts

Commands Run (additional)

  • cd server && npx vitest run src/test/unit/product/serverActionProductAccess.contract.test.ts
    • Result: pass (2 tests).
  • (2026-05-06) Completed F349/F350/F351/F352/F353 via explicit PSA-preservation contract coverage.
  • Added server/src/test/unit/product/psaPreservation.contract.test.ts to assert PSA fallback branches remain in:
    • MSP dashboard/settings/tickets/client-detail composition files
    • client portal sidebar default PSA composition branch
  • Command run:
    • cd server && npx vitest run src/test/unit/product/psaPreservation.contract.test.ts src/test/unit/product/serverActionProductAccess.contract.test.ts
    • Result: pass (4 tests).
  • (2026-05-06) Completed T010 with DB-backed AlgaDesk ticket create/update integration coverage plus RBAC enforcement contract assertions.
    • Added server/src/test/integration/algadeskTicketCrudRbac.integration.test.ts:
      • Creates an AlgaDesk-flagged tenant fixture and verifies ticket creation with client/contact/board/category/priority/assignee fields using TicketModel.createTicket.
      • Verifies status + response_state update (awaiting_client) using TicketModel.updateTicket.
      • Verifies persisted ticket row contains expected create/update values.
      • Adds static assertions that packages/tickets/src/actions/ticketActions.ts retains explicit RBAC hasPermission checks and permission-denied error paths for create/update flows.
    • Command run:
      • cd server && npx vitest run src/test/integration/algadeskTicketCrudRbac.integration.test.ts
      • Result: suite execution blocked in this environment by missing local Postgres (ECONNREFUSED on localhost:5432), consistent with existing DB-backed integration constraints.
  • (2026-05-06) Completed T011 with DB-backed AlgaDesk ticket-attachment draft cleanup integration and composition-boundary assertions.
    • Added server/src/test/integration/algadeskTicketAttachmentDrafts.integration.test.ts:
      • Seeds AlgaDesk tenant/ticket/documents and validates deleteDraftClipboardImages deletes only ticket-scoped image drafts without cross-entity associations.
      • Verifies blocked deletion reason (has_other_associations) for attachments also linked to non-ticket entities.
      • Verifies RBAC denial path for document delete permission.
      • Asserts AlgaDesk composition/document component source keeps folder-selection/share/linking restrictions (disableAttachmentFolderSelection, disableAttachmentSharing, disableAttachmentLinking, and guarded Documents behaviors).
    • Command run:
      • cd server && npx vitest run src/test/integration/algadeskTicketAttachmentDrafts.integration.test.ts
      • Result: suite execution blocked in this environment by missing local Postgres (ECONNREFUSED on localhost:5432), consistent with existing DB-backed integration constraints.
  • (2026-05-06) Completed T018/T019/T020 with focused route/API boundary smoke coverage.
    • Added server/src/test/unit/product/algadeskRouteAndApiBoundarySmoke.test.ts:
      • Verifies representative AlgaDesk MSP direct-hit route outcomes resolve to upgrade/not-found boundaries while PSA stays pass-through.
      • Verifies representative allowed (tickets/clients/contacts/knowledge-base/email) and denied (billing/projects/assets/time/workflows/extensions/chat/surveys/documents) API group behavior for AlgaDesk.
      • Verifies metadata visibility rules (isApiVisibleInMetadata) hide denied API paths for AlgaDesk while preserving PSA visibility.
      • Asserts ApiMetadataController source continues to apply metadata/OpenAPI path filtering with isApiVisibleInMetadata.
    • Command run:
      • cd server && npx vitest run src/test/unit/product/algadeskRouteAndApiBoundarySmoke.test.ts
      • Result: pass (3 tests).
  • (2026-05-06) Completed T016/T017 by pinning existing inbound-email DB/integration coverage artifacts and reply-threading/dedupe paths.
    • Added server/src/test/unit/email/algadeskInboundEmailDbCoverage.contract.test.ts:
      • Asserts inbound webhook integration suite includes ticket-creation scenarios with defaults mapping and unmatched-sender handling.
      • Asserts inbound webhook integration suite includes dedupe assertions for repeated events.
      • Asserts shared inbound processing suites include reply-token/thread-header matching coverage paths used for reply comment threading.
    • Command run:
      • cd server && npx vitest run src/test/unit/email/algadeskInboundEmailDbCoverage.contract.test.ts
      • Result: pass (2 tests).
  • (2026-05-06) Completed T015 by adding a dedicated Playwright portal happy-path scenario.
    • Added server/src/test/e2e/algadesk-portal-ticketing.playwright.test.ts:
      • Creates an AlgaDesk tenant context and portal contact session.
      • Validates portal ticket creation flow with free-form fields and attachment upload.
      • Seeds internal/public technician comments and verifies client portal only shows public content.
      • Validates portal contact public reply path and confirms persisted public comment.
    • Execution note:
      • This Playwright scenario was added but not executed in this environment during this pass.
  • (2026-05-06) Supersession note: remediation implementation tracking is now authoritative in ee/docs/plans/2026-05-06-algadesk-remediation-product-seam/ until all remediation blockers are resolved.

Smoke Notes — 2026-05-06 Batch 1 Preflight

  • Environment: local Next dev server at http://localhost:3234 from worktree feature/algadesk-lightweight-ticketing.
  • Seed/login accounts validated:
    • AlgaDesk MSP/internal: glinda@emeraldcity.oz / TestPassword123! on Oz tenant 2313304f-0253-48fb-8a34-af237f9d1111 after setting tenants.product_code='algadesk' for smoke coverage.
    • AlgaDesk client portal: casey.client.admin@example.com / TestPassword123! linked to Acme Managed Services.
    • Smoke Tenant B MSP admin also validated for AlgaDesk shell reachability (algadesk.admin@emeraldcity.oz), but its portal ticket list still failed due local fixture/setup drift, so Oz remained the richer browser-smoke tenant.
  • Fixed smoke blockers found during browser execution:
    • Split client-safe ticket attachment URL resolution out of a 'use server' action file into packages/msp-composition/src/tickets/ticketAttachmentViewUrl.ts.
    • Added DrawerProvider / DrawerOutlet and documents cross-feature provider to server/src/components/layout/AlgaDeskMspShell.tsx so AlgaDesk ticket detail can render.
    • Hid AlgaDesk-excluded MSP ticket detail surfaces: SLA status, project task action, time entry/timer controls, materials, surveys, asset panels, link-existing-documents, share/folder choices, and rich-text document creation. Upload-file ticket attachments remain visible.
    • Narrowed AlgaDesk client portal dashboard/details surfaces: dashboard now shows ticket-focused content only, portal sidebar fallback brand is AlgaDesk, portal ticket detail hides appointment requests plus document linking/rich-text document creation while retaining upload-file attachments.
  • Browser evidence:
    • /msp/dashboard renders AlgaDesk shell/nav only: Home, Tickets, Clients, Contacts, Documents -> Knowledge Base, Settings, Support.
    • /msp/tickets renders list/quick-add with no SLA filter text.
    • Created MSP ticket TIC001012 (2cd4d8bd-3ea1-4efe-9dd8-6c2cce6f7cdc) titled AlgaDesk smoke MSP quick add 1778088619540.
    • MSP ticket detail after fixes contains Upload File attachment support and no SLA, Time Entry, Ticket Timer, Add Time Entry, Materials, Project Task, Asset, Survey, AI, Link Documents, or New Document text.
    • Portal dashboard after fixes contains Open Support Tickets + ticket recent activity only; no Open Projects, Service requests, Upcoming visits, Active devices, billing/projects/devices/documents nav labels, or appointment CTA.
    • Created portal ticket TIC001013 (8fbde819-48d2-4efa-bbb5-be702711a026) titled AlgaDesk portal smoke ticket 1778089095962.
    • Portal ticket detail after fixes contains Upload File attachment support and no Link Documents, New Document, Appointment, Projects, Devices, Billing, or Internal text.
    • Direct-hit excluded routes /msp/billing, /msp/projects, /msp/assets, /client-portal/billing, /client-portal/projects, /client-portal/devices, /client-portal/documents, and /client-portal/request-services show the branded Alga PSA upgrade boundary.
    • PSA control tenant Smoke Tenant A 1777158361 (smoke-a-1777158361@example.test) still renders the full AlgaPSA MSP shell with PSA modules such as Billing, Projects, Assets, Time Management, Schedule, Workflows, and Extensions.
  • Batch 1 test 1 /msp/tickets result: PASS for page load, Ticketing Dashboard, Add/Import/Export controls, assignee/status/response/priority/due-date/category/search/density controls. Observed status control text is All open statuses with aria-label Select Status; search is present as placeholder Search tickets.... SLA Status is intentionally absent for AlgaDesk per PRD/T008 (allowSlaStatusFilter=false), so any smoke expectation requiring SLA Status should be corrected for AlgaDesk.
  • Batch 1 test 2 /msp/tickets Algadev result: PASS after clean AlgaDesk MSP session. Sidebar brand is AlgaDesk; top-level nav is Home, Tickets, Clients, Contacts, Documents, Settings, Support; Documents expands to Knowledge Base. PSA-only nav labels are absent: User Activities, Service Requests, Surveys, Projects, Assets, Time Management, Billing, Schedule, Technician Dispatch, Workflows, System Monitoring, Extensions. Note: the existing Algadev browser had a stale pre-clean-session shell until sign-out/sign-in; after clean login it rendered the correct AlgaDesk shell.
  • Batch 1 test 3 /msp/tickets Algadev result: PASS. Inspected AlgaDesk sidebar with Documents expanded (Knowledge Base only) and Settings expanded (General, Profile, Security only). Normal navigation does not expose User Activities, Service Requests, Surveys, Projects, All Documents, Assets, Time Management, Time Entry, Approvals, Billing, Schedule, Technician Dispatch, Workflows, Control Panel, Workflow Editor, System Monitoring, Job Monitoring, Extensions, or Reports.
  • Batch 1 test 4 /msp/settings Algadev result: PASS. Settings shell shows reduced support-relevant sections only: Organization & Access (General, Users, Teams, Client Portal), Work Management (Ticketing, Knowledge Base), Communication (Email). Settings links do not include SLA, Projects, Interactions, Time Entry, Billing, Notifications, Integrations, Extensions, Import/Export, Secrets, or Experimental Features. Representative direct query attempts (?tab=billing, sla, projects, interactions, time-entry, notifications, integrations, extensions, import-export, secrets, experimental-features) all fell back to General Settings.
  • Batch 1 test 5 Algadev allowed MSP destination result: PASS. Opened /msp/tickets, /msp/clients, /msp/contacts, /msp/knowledge-base, and /msp/settings as AlgaDesk MSP user. None showed product-boundary or not-found errors. /msp/tickets showed Ticketing Dashboard. /msp/clients showed + Create Client, Actions, Status Filter, Client Type Filter, and a Search clients input placeholder. /msp/contacts, /msp/knowledge-base, and /msp/settings loaded their allowed operator pages normally.
  • Batch 1 test 6 /msp/clients Algadev result: PASS with one implementation fix. List page shows + Create Client, Actions, Search clients input, Active Clients, All Types, and table columns Name, Created, Type, Phone, Address, Account Manager, URL, Tags, Actions. Actions menu exposes Upload CSV and Download CSV when opened by keyboard. Status/type dropdown option text is present for Active Clients / Inactive Clients / All Clients and All Types / Companies / Individuals. Client detail initially failed with useClientCrossFeature must be used within a ClientCrossFeatureProvider; fixed by adding an AlgaDesk-safe client cross-feature provider to the AlgaDesk shell. After fix, opening Acme Managed Services loads the client detail support surface without product-boundary/not-found error and without Billing/Projects/Assets/Contracts/Surveys surfaces.
  • Batch 1 test 7 /msp/tickets Algadev result: PASS. Typed printer into Search tickets... (input accepted value), toggled Bundled to individual view (?bundleView=individual), navigated to /msp/clients, returned to /msp/tickets, and refreshed. The app did not blank, redirect-loop, or show product-boundary/product-denied/unexpected error text; /msp/tickets returned to the Ticketing Dashboard operator surface after navigation and refresh. Note: the search text cleared when toggling bundled view because that control updates route state; this did not affect stability.

Smoke Notes — 2026-05-06 Phase 4 Client Portal Ticketing

  • Environment: local app http://localhost:3234, Algadev browser pane b08fcb86-3dd1-49a2-8a3d-0b1135b88ec1.
  • Portal contact: casey.client.admin@example.com on AlgaDesk tenant 2313304f-0253-48fb-8a34-af237f9d1111 (2313309d1111 portal slug).
  • Phase 4 tests 2-9 result: PASS.
    • Portal navigation exposes AlgaDesk support items only: Dashboard, Tickets, Knowledge Base, Profile, and permission-gated Client Settings. It does not show Request Services, Projects, Appointments, My devices, Documents, Extensions, or Billing.
    • /client-portal/tickets controls present: Select Status (All Statuses), Response Status (All Response Statuses), All Priorities, Filter by category, Search tickets..., Reset, and Create Support Ticket. Reset is present and becomes visible once a filter/search is active.
    • Ticket table columns present: Ticket Number, Title, Status, Priority, Due Date, Assigned To, Created, Last Updated.
    • Create Support Ticket dialog opens with title input, rich text description editor, board selector, priority selector, Cancel, and Create Ticket. Board auto-selected to the only visible board (Acme Support Board); priority choices included Whimsical Wish, Curious Conundrum, and Enchanted Emergency.
    • Required-field validation with empty submit showed: Title is required, Description is required, and Please select a priority. Board validation did not show because a board was already selected.
    • Created portal ticket TIC001015 (e26eebb4-e63e-4bb8-b702-57d0dca5fd40) titled Smoke Portal Cannot Access VPN 2026-05-06-001, board Acme Support Board, priority Curious Conundrum.
    • Searching for Cannot Access VPN 2026-05-06-001 narrowed the list to the newly created ticket with status Curious Beginning, priority Curious Conundrum, and Created/Last Updated timestamps.
    • Opened the new ticket detail from the list. Detail page shows Back to Tickets, #TIC001015, Created, Last Updated, title, Status, Assigned To, Priority, Due Date, and Description with the entered customer issue text.
  • Phase 4 tests 10-11 result: PASS.
    • On portal ticket TIC001015, opened Status dropdown, selected Unfolding Adventure, observed confirmation dialog Change Status with Cancel/Update, clicked Update, and verified detail status changed from Curious Beginning to Unfolding Adventure.
    • Added public client portal comment: Smoke customer follow-up: I am still unable to connect to the VPN after rebooting, and I can reproduce the timeout on both Wi-Fi and ethernet. The comment appeared under All Comments as Casey Client Admin (Client), Received via Client Portal; ticket response state updated to Awaiting Support Response.
  • Phase 4 tests 12-16 result: PASS with noted /client-portal/extensions blocked-via-404 behavior.
    • Comments area tabs are customer-safe only: All Comments and Resolution. No Internal tab or Mark as Internal control appeared.
    • Added a resolution-marked portal comment on TIC001015: Smoke resolution note: VPN access was restored after resetting the account token and asking the user to reconnect with the updated profile. Toggled Mark as Resolution to Marked as Resolution, submitted, then verified the comment appears in the Resolution view. No MSP-only close/status controls were exposed.
    • Ticket Documents section remains upload-focused: Documents, Upload File, No documents found; no link-existing-documents, new document, sharing, blocking, folder, move, permissions, or document-type controls.
    • Back to Tickets returned to /client-portal/tickets; TIC001015 remained visible and searchable by Cannot Access VPN 2026-05-06-001.
    • Direct-hit excluded routes while signed in as Casey:
      • /client-portal/projects: Alga PSA product boundary (Available in Alga PSA).
      • /client-portal/appointments: Alga PSA product boundary.
      • /client-portal/devices: Alga PSA product boundary.
      • /client-portal/documents: Alga PSA product boundary.
      • /client-portal/request-services: Alga PSA product boundary (Service Requests).
      • /client-portal/extensions: blocked with 404 - Page Not Found; no PSA portal content rendered. This matches the earlier smoke decision to accept extension 404 as blocked/no-leak behavior, though it is not the branded product-boundary screen.

Smoke Notes — 2026-05-06 Phase 5 Email Preflight

  • Environment: local app http://localhost:3234, Algadev browser pane b08fcb86-3dd1-49a2-8a3d-0b1135b88ec1.
  • Signed in as AlgaDesk MSP/internal user glinda@emeraldcity.oz on tenant 2313304f-0253-48fb-8a34-af237f9d1111.
  • Phase 5 preflight:
    • Unique subject reserved: Smoke Email Cannot Print 2026-05-06-001.
    • Known client/contact candidate with non-example email: Alice in Wonderland <alice@wonderland.com> for client Bravo Retail Group.
    • Email provider already configured for the tenant: IMAP provider Local EML HTML parsing test 1777127896556, mailbox support@nineminds.com, active=true, status=connected.
  • Phase 5 test 1 result: PASS.
    • Opened /msp/settings?tab=email as AlgaDesk MSP admin.
    • Settings sidebar includes Communication → Email.
    • Email configuration page loaded without product-boundary/PSA-only block.
    • Page shows Email Provider Configuration, inbound setup copy, Add Email Provider, and existing provider summary Gmail: 0 · IMAP: 1, Email Providers (1), support@nineminds.com, IMAP, Connected, Active.
  • Phase 5 tests 2-10 result: PASS for UI/provider/defaults surfaces and inbound-processing ticket behavior; SMTP/IMAP transport was simulated through the application inbound-email workflow action because the local GreenMail/test mailbox ports (3025/3143) were not running in this environment.
    • Test 2: Email settings Providers/Defaults surface showed Providers, Defaults, Add Email Provider, and non-EE copy Configure Gmail or IMAP providers to receive and process inbound emails as tickets.
    • Test 3: Add Email Provider opened Choose Email Provider; available provider cards/actions in this edition were Gmail (Set up Gmail) and IMAP (Set up IMAP). Microsoft 365 was not shown in this CE-style configuration.
    • Test 4: Defaults section showed existing General Email Support mapping and the create form fields: Short Name *, Display Name *, Description, Active, Ticket Defaults, Board *, Status *, Priority *, Client *, Category, Location, and Entered By.
    • Test 5: Attempting to create incomplete defaults was blocked. Browser native validation protects empty Short Name * / Display Name *; after filling those and leaving ticket defaults incomplete, the form showed Board is required and did not create Smoke Incomplete Defaults 20260506.
    • Test 6: Simulated a new inbound email from Alice in Wonderland <alice@wonderland.com> to support@nineminds.com with subject Smoke Email Cannot Print 2026-05-06-001 and body Smoke inbound email body: The customer cannot print from the front desk workstation. The printer queue shows stuck jobs and restarting the printer did not clear the issue. Please investigate before the morning shipping run. via shared/workflow/actions/emailWorkflowActions.createTicketFromEmail using the configured IMAP provider/defaults.
    • Created email-origin ticket TIC001016 (a56521ad-59f8-48b1-8ac4-ef5d0d3d5b61), client Bravo Retail Group, contact Alice in Wonderland, board Acme Support Board, status Curious Beginning, priority Curious Conundrum, origin inbound_email.
    • Test 7: /msp/tickets search for the unique subject returned TIC001016 with columns Ticket Number, Title, Status, Priority, Board, Category, Client, Assigned To, Due Date, Created, Created By, Tags, Actions. Row showed Bravo Retail Group and expected title/status/priority.
    • Test 8: Ticket detail showed origin badge Created via Inbound Email.
    • Test 9: Email body appeared in the ticket Description with enough fidelity to understand the issue.
    • Test 10: Simulated customer reply with shared/workflow/actions/emailWorkflowActions.createCommentFromEmail: Smoke follow-up: printer shows paper jam error. Database count for subject remained 1, and the existing TIC001016 detail showed the reply under Comments as Alice in Wonderland, Received via Inbound Email; no duplicate ticket was created.
    • Note: event-bus publishes from standalone scripts hit local Redis auth/env mismatch and were skipped/errored after persistence; DB/UI persistence was successful.
  • Phase 5 tests 11-16 result: PASS for UI/persistence/scope behavior; outbound customer-email delivery was not verified because ticket email notification logs showed No email notifications found in this local environment.
    • Test 11: Refreshed TIC001016; Comments showed customer reply Smoke follow-up: printer shows paper jam error. with response source indicator Received via Inbound Email under Alice in Wonderland.
    • Test 12: Added public MSP reply with Mark as Internal off: Smoke public support reply: We received the printer issue and are checking the print queue now. Please leave the printer powered on. It appeared under All Comments and Client; response state changed to Awaiting Client. Email notification log dialog showed No email notifications found, so outbound delivery was not configured/observable here.
    • Test 13: Added internal note with Marked as Internal: Smoke internal note: Do not email the customer with spooler credentials; confirm printer admin password rotation first. It appeared under Internal and was absent from Client.
    • Test 14: Added resolution-marked comment: Smoke email resolution: Cleared the printer queue and confirmed the test page printed successfully after restarting the spooler. It appeared under Resolution. No close status was selected, so ticket status remained Curious Beginning.
    • Test 15: Simulated unknown sender inbound email (live SMTP/IMAP not running) from unknown.sender.20260506@outside-smoke.invalid with subject Smoke Unknown Sender Email 2026-05-06-001. The system created catch-all/defaults ticket TIC001017 (0b5e0a7b-bf3a-4039-894d-90dc4113a99c) with client Bravo Retail Group, no contact, and ticket_origin='inbound_email'; no crash/loss.
    • Test 16: Inspected email-created TIC001016 detail. It remained within AlgaDesk helpdesk scope: ticket origin badge, support ticket fields, comments, and upload-focused Documents section. PSA-only surfaces/actions were absent: Time Entry, Add Time Entry, Ticket Timer, Materials, Asset(s), Project Task, Link Documents, New Document, Share, Folder, Block Document, SLA Status, Survey Summary, and AI.

Smoke Notes — 2026-05-06 Phase 6 Attachment Preflight

  • Prepared small attachment fixtures:
    • /tmp/algadesk-smoke-attachments/smoke-screenshot.png
    • /tmp/algadesk-smoke-attachments/smoke-log.txt
  • Selected existing shared-access tickets:
    • MSP-created ticket: TIC001014 (c9d2e1b1-f290-4b0a-b6b0-56bbba6611b0), Acme Managed Services, visible to AlgaDesk MSP and previously visible in Casey's client portal list.
    • Portal-created ticket: TIC001015 (e26eebb4-e63e-4bb8-b702-57d0dca5fd40), created by Casey portal contact and visible to MSP.
  • Phase 6 test 1 result: PASS.
    • As AlgaDesk MSP agent, opened /msp/tickets/c9d2e1b1-f290-4b0a-b6b0-56bbba6611b0.
    • Ticket detail shows an in-ticket Documents section with Upload File.
    • This is the ticket attachment area, not the full /msp/documents document-management module. Full module signals were absent from the ticket detail: All Documents, Document Management, Folders, Storage Usage, Knowledge Base Articles.
  • Phase 6 tests 2-7 result: PASS.
    • Test 2: In TIC001014 Documents section, clicked Upload File; upload panel opened with Drag and drop your files here, or, Browse Files, and Cancel.
    • Test 3: Uploaded smoke-screenshot.png (68 byte PNG fixture). The file appeared in the ticket Documents grid as smoke-screenshot.png. The upload completed quickly enough that the transient Uploading... text was not captured in polling, but completion and persisted document card were verified.
    • Test 4: Uploaded document card shows real metadata: name smoke-screenshot.png, uploader/date Paula Policy Admin • 5/6/2026, Type: image/png, visibility badge Internal, file type image/png, and size 0.1 KB. Ticket-level actions present by button id: download (download-document-a65af5b8-4c06-4c33-88ee-83edda4392fa-button), detach/disassociate (disassociate-document-a65af5b8-4c06-4c33-88ee-83edda4392fa-button), and delete (delete-document-a65af5b8-4c06-4c33-88ee-83edda4392fa-button).
    • Test 5: Download endpoint for the uploaded document returned HTTP 200 with content-type: image/png, content-disposition: attachment; filename="smoke-screenshot.png"; filename*=UTF-8''smoke-screenshot.png, and 68 byte blob size. Browser download click did not create a visible file in ~/Downloads in this harness, so endpoint/content-disposition was used as download evidence.
    • Test 6: Documents section allows Upload File and does not show full document creation/linking actions New Document or Link Documents.
    • Test 7: Uploaded document card actions do not include Share; AlgaDesk ticket attachment card did not expose full PSA document sharing.
  • Phase 6 tests 8-15 result: PASS.
    • Test 8: On MSP-uploaded smoke-screenshot.png, clicked detach/disassociate. Confirmation dialog appeared with title Detach Document and buttons Cancel / Detach. Confirming Detach removed the attachment from TIC001014. Re-uploaded smoke-screenshot.png afterward for visibility/scope checks; new document id ce16ebba-66ef-4efb-87fb-f8bf4b37f17c, visibility Internal.
    • Test 9: As Casey in the AlgaDesk client portal, opened /client-portal/tickets/c9d2e1b1-f290-4b0a-b6b0-56bbba6611b0; ticket detail shows a ticket-level Documents section with Upload File.
    • Test 10: In portal Documents section, clicked Upload File; upload panel showed Drag and drop your files here, or, Browse Files, and Cancel. Uploaded smoke-log.txt; it appeared in the ticket Documents grid with name smoke-log.txt, uploader/date, Type: text/plain, and size 0.1 KB. Upload completed quickly, so transient uploading text was not captured.
    • Test 11: Portal attachment download endpoint /api/documents/download/cd377ec9-acd6-42d1-a9a4-4a6cbdc3c084 returned HTTP 200 with content-type: text/plain, content-disposition: attachment; filename="smoke-log.txt"; filename*=UTF-8''smoke-log.txt, size 59 bytes, and expected file text.
    • Test 12: Portal ticket Documents section allows Upload File and download for smoke-log.txt; it does not show Link Documents, New Document, Share, blocking controls, folder/all-documents/document-library/document-management/storage controls, or broader document-library controls.
    • Test 13: As AlgaDesk MSP, reopened TIC001014; ticket Documents section retained scoped attachments smoke-log.txt and smoke-screenshot.png. Direct /msp/documents showed product boundary Available in Alga PSA and did not render document-management module controls.
    • Test 14: As Casey portal user, direct /client-portal/documents showed product boundary Available in Alga PSA and did not render the PSA document library.
    • Test 15: Attachment visibility was consistent. MSP-uploaded smoke-screenshot.png is marked Internal and did not appear in the client portal view of TIC001014; portal-uploaded smoke-log.txt is client-visible and appeared in both portal and MSP ticket Documents sections. MSP cards still show visibility state controls/badges (Internal / Client visible) in AlgaDesk, as expected by the smoke note.

Smoke Notes — 2026-05-06 Phase 7 Knowledge Base Preflight

  • Prepared three KB articles with unique prefix ALGADESK-KB-SMOKE on AlgaDesk tenant 2313304f-0253-48fb-8a34-af237f9d1111:
    • ALGADESK-KB-SMOKE Draft Internal 2026-05-06-001: status draft, audience internal, client-visible false.
    • ALGADESK-KB-SMOKE Published Client 2026-05-06-001: status published, audience client, client-visible true.
    • ALGADESK-KB-SMOKE Published Public 2026-05-06-001: status published, audience public, client-visible true.
  • Phase 7 test 1 result: PASS.
    • As AlgaDesk MSP user glinda@emeraldcity.oz, opened /msp/knowledge-base.
    • Page shows Knowledge Base with subtitle Create and manage knowledge base articles.
    • Tabs show Articles and Review Dashboard.
    • Page is not blocked by product boundary and lists the prepared smoke articles in Knowledge Base Articles.
  • Phase 7 tests 2-7 result: PASS except test 3 has a filter-surface gap.
    • Test 2: Article table columns present: Title, Type, Audience, Status, Tags, Stats, Updated, Actions.
    • Test 3: Filter sidebar present with Filters, Search, Status, Audience, and Article Type; search placeholder is Search articles.... Gap: Category filter was not visible even though the tenant has categories, and Tags filter was not visible because no KB tags are currently available/mapped. Code note: KBArticleFilters supports categories, but KnowledgeBasePage currently renders it without passing categories.
    • Test 4: Clicked New Article; editor opened for New Article with status Draft.
    • Test 5: Updated metadata for the new article to title ALGADESK-KB-SMOKE Created Client FAQ 2026-05-06-001, slug algadesk-kb-smoke-created-client-faq-2026-05-06-001, Article Type FAQ, Audience Client, Review Cycle 30 days, then clicked Save Metadata. Returned to list showing the updated row as FAQ, Client, Draft. DB verification after publish shows slug/type/audience/review cycle persisted (review_cycle_days=30, next_review_due=2026-06-05).
    • Test 6: KB editor sidebar shows Statistics with counters Views, Helpful, and Not helpful, all 0 for the new article.
    • Test 7: Clicked Publish on the client-audience draft article. Editor changed status to Published; returning to the list showed the row with Client audience and Published status.
  • Fixed Phase 7 test 3 Category filter gap:
    • Added getKnowledgeBaseCategories in packages/documents/src/actions/kbArticleActions.ts to load tenant categories for KB metadata/filtering.
    • Wired loaded categories through packages/documents/src/components/kb/KnowledgeBasePage.tsx into KBArticleFilters and KBArticleEditor.
    • Revalidated /msp/knowledge-base: filter sidebar now shows Category with All Categories; category options include tenant categories such as Magical Artifacts, Network / VPN, and Service Request / Access Request.
    • Validation: npx eslint packages/documents/src/actions/kbArticleActions.ts packages/documents/src/components/kb/KnowledgeBasePage.tsx --quiet passed.
  • Fixed KB metadata save blank/navigation issue:
    • Root cause: KBArticleEditor.handleSaveMetadata called the parent onSave callback, and KnowledgeBasePage wired that callback to handleBack, causing metadata save to navigate away from the article editor while the editor was still reloading article state.
    • Fix: Metadata save now reloads the article in place and keeps the editor open; removed the onSave={handleBack} wiring from KnowledgeBasePage and removed the editor onSave prop/call.
    • Revalidated in Algadev on article 8359aa47-f2d1-4b9f-8c1c-5e6584b26bd1: changed Review Cycle from 30 days to 60 days, clicked Save Metadata, and the URL remained /msp/knowledge-base?article=8359aa47-f2d1-4b9f-8c1c-5e6584b26bd1 with the editor/Statistics/Metadata still visible. Changed it back to 30 days and saved again with the editor still visible.
    • Validation: npx eslint packages/documents/src/components/kb/KBArticleEditor.tsx packages/documents/src/components/kb/KnowledgeBasePage.tsx --quiet passed.
  • Phase 7 tests 8-18 result: PASS after two KB editor fixes.
    • Prepared supplemental smoke data for review/archive/delete/portal filtering/pagination:
      • ALGADESK-KB-SMOKE Review Candidate 2026-05-06-001 (4c03643a-1cb6-490a-9624-e84a9a2a927f), initially Draft/Internal.
      • ALGADESK-KB-SMOKE Archive Delete Candidate 2026-05-06-002 (38f8b0a1-e55e-4b97-a93c-69860087f0df), Published/Client, used for archive/delete validation and then deleted.
      • Hidden portal-negative articles: Internal/Published, Client/Draft, Client/Archived.
      • 22 published client pagination articles named ALGADESK-KB-SMOKE Pagination Published Client NN 2026-05-06-001.
      • Tag ALGADESK-KB-SMOKE-TAG mapped to ALGADESK-KB-SMOKE Published Client 2026-05-06-001.
    • Test 8: Opened review candidate and clicked Submit for Review; status changed from Draft to In Review.
    • Test 9: Opened /msp/knowledge-base/review; Review Dashboard loaded and showed Articles Awaiting Review with the smoke review candidate.
    • Test 10: Added missing archive confirmation behavior in KBArticleEditor. Revalidated published archive/delete candidate: clicking Archive opened Archive Article confirmation; confirming changed status to Archived and surfaced Delete permanently.
    • Test 11: Delete permanently on archived article opened Delete Article confirmation. Confirming deleted the article and returned to /msp/knowledge-base list with the title absent. Also fixed stale editor/delete navigation by closing the dialog, clearing article state, and navigating via onBack/router on the next tick.
    • Test 12: As Casey client portal user, opened /client-portal/knowledge-base; portal showed the Knowledge Base surface and sidebar included Knowledge Base under RESOURCES.
    • Test 13: Portal search placeholder is Search articles...; searching ALGADESK-KB-SMOKE Published Client returned the published client article and did not show draft/internal smoke articles.
    • Test 14: Portal tag filter was available because smoke tag data exists. Opened Filter by tags..., selected ALGADESK-KB-SMOKE-TAG; list narrowed to one tagged published article (ALGADESK-KB-SMOKE Published Client 2026-05-06-001).
    • Test 15: Opened published article detail from portal. Detail showed title, How-To type badge, 1 views, and Was this article helpful?. Clicking Yes accepted feedback and changed prompt to Thank you for your feedback!; MSP editor controls/buttons were absent.
    • Test 16: Portal searches for ALGADESK-KB-SMOKE Hidden Internal Published, ALGADESK-KB-SMOKE Hidden Client Draft, and ALGADESK-KB-SMOKE Hidden Client Archived each returned 0 articles / No articles found.
    • Test 17: Portal KB pagination showed Previous, Page 1 of 2, and Next; clicking Next moved to Page 2 of 2, clicking Previous returned to Page 1 of 2, and neither page exposed hidden/internal/draft/review articles.
    • Test 18: As AlgaDesk MSP user, /msp/knowledge-base remained allowed. Direct /msp/documents showed product boundary Available in Alga PSA and did not expose full document-management controls (Document Management, All Documents, Storage Usage, Folders).
    • Validation: npx eslint packages/documents/src/actions/kbArticleActions.ts packages/documents/src/components/kb/KnowledgeBasePage.tsx packages/documents/src/components/kb/KBArticleEditor.tsx --quiet passed.
  • Phase 8 preflight + test 1 result: PASS after smoke API-key permission preflight.
    • Created a local AlgaDesk tenant API key for tenant 2313304f-0253-48fb-8a34-af237f9d1111 owned by broad-permission MSP user 241a34f1-950c-45ce-994a-47b8ebd7e6dc (glinda@emeraldcity.oz). Plaintext key intentionally not recorded here.
    • Preflight gap: the Admin role did not have metadata:read, so metadata endpoints initially returned 403 Permission denied: Cannot read metadata. Added tenant permission metadata/read and assigned it to the tenant Admin role for the smoke DB.
    • Revalidated metadata endpoints with x-api-key and x-tenant-id headers against http://localhost:3234:
      • GET /api/v1/meta/endpoints: 200, JSON top-level data and meta; data keys include endpoints, totalEndpoints, categories, version, lastUpdated.
      • GET /api/v1/meta/schemas: 200, JSON top-level data and meta; data keys include schemas, totalSchemas, categories.
      • GET /api/v1/meta/permissions: 200, JSON top-level data and meta; data keys include permissions, totalPermissions, categories.
      • GET /api/v1/meta/openapi: 200, OpenAPI JSON top-level openapi, info, servers, paths, components, security, tags; filtered AlgaDesk spec had 15 paths.
      • GET /api/v1/meta/health: 200, JSON top-level data and meta; data keys include status, version, timestamp, uptime, services, metrics.
      • GET /api/v1/meta/stats: 200, JSON top-level data and meta; data keys include totalEndpoints, endpointsByCategory, endpointsByMethod, totalSchemas, totalPermissions, coverage.
  • Phase 8 tests 2-12 result: PASS after API metadata/product-boundary fixes.
    • Fixes made:
      • server/src/lib/api/services/MetadataService.ts: endpoint discovery now detects export const GET = ... style route handlers in addition to export async function GET(...); development endpoint discovery cache is bypassed so hot-added route files appear immediately in smoke metadata.
      • Added minimal API-key-authenticated top-level route stubs for /api/v1/billing, /api/v1/workflows, /api/v1/documents, /api/v1/financial, /api/v1/comments, and /api/v1/email so product-boundary and metadata behavior is explicit at those paths. For PSA tenants these stubs return ordinary 404 NOT_FOUND; for AlgaDesk the product gate returns 403 PRODUCT_ACCESS_DENIED before the stub handler.
      • Wrapped /api/v1/assets and /api/v1/contracts top-level routes with API-key auth/product gating so AlgaDesk denial happens before controller context/validation failures.
      • Added a GET handler to /api/v1/tickets/from-asset that goes through API-key auth/product gating, returning product denial for AlgaDesk before normal method behavior.
    • Test 2: /api/v1/meta/endpoints listed AlgaDesk-visible APIs including /api/v1/tickets, /api/v1/clients, /api/v1/contacts, /api/v1/kb-articles, /api/v1/comments, /api/v1/boards, /api/v1/statuses, /api/v1/priorities, /api/v1/tags, /api/v1/users, /api/v1/teams, /api/v1/email, and /api/v1/meta/*. PSA-only prefixes were absent: billing, projects, assets, time-entries, documents, workflows, surveys, financial, contracts, quotes.
    • Test 3: data.totalEndpoints === data.endpoints.length (137 === 137), with 92 unique visible paths; count reflects the filtered AlgaDesk list rather than PSA-wide discovery.
    • Test 4: /api/v1/meta/openapi included /api/v1/tickets; pruned paths omitted PSA-only prefixes /api/v1/billing, /api/v1/projects, /api/v1/assets, and /api/v1/documents. Filtered OpenAPI path count: 92.
    • Test 5: /api/v1/meta/schemas categories were limited to Clients, Email, Knowledge Base, Other, and Tickets. Schema name scan found zero matches for denied terms: billing, invoice, quote, contract, project, asset, workflow, survey, accounting, financial, payment, tax, service catalog, service type, product.
    • Test 6: /api/v1/meta/stats reflected AlgaDesk-visible metadata: totalEndpoints=137, categories Other=88, Clients=17, Email=1, Knowledge Base=12, Tickets=19; methods GET=65, PUT=21, DELETE=22, POST=29; totalPermissions=39, totalSchemas=302.
    • Test 7: /api/v1/meta/health data.metrics.totalEndpoints matched the filtered visible endpoint count (137).
    • Test 8: Representative allowed helpdesk APIs all returned normal success (200) and not product denial: /api/v1/tickets, /api/v1/clients, /api/v1/contacts, /api/v1/kb-articles, /api/v1/boards, /api/v1/statuses, /api/v1/priorities, /api/v1/tags.
    • Test 9: Representative PSA-only APIs all returned 403 PRODUCT_ACCESS_DENIED: /api/v1/billing, /api/v1/projects, /api/v1/assets, /api/v1/time-entries, /api/v1/workflows, /api/v1/documents, /api/v1/financial, /api/v1/contracts, /api/v1/quotes, /api/v1/service-types.
    • Test 10: PSA-only ticket subroutes returned 403 PRODUCT_ACCESS_DENIED: /api/v1/tickets/from-asset, /api/v1/tickets/c9d2e1b1-f290-4b0a-b6b0-56bbba6611b0/time-entries, /api/v1/tickets/c9d2e1b1-f290-4b0a-b6b0-56bbba6611b0/materials.
    • Test 11: Denied endpoints with intentionally invalid/incomplete parameters still returned product denial first, not validation leaks: /api/v1/service-types?billing_method=bogus and /api/v1/assets?page=not-a-number both returned 403 PRODUCT_ACCESS_DENIED.
    • Test 12: PSA regression key/tenant was prepared for Smoke Tenant A (d4bc19b0-f113-408a-a3b1-be2d44cae4a6). Added metadata/read to its Admin role for metadata smoke. PSA calls were not blocked by AlgaDesk rules: /api/v1/meta/endpoints returned 200; /api/v1/billing returned ordinary 404 NOT_FOUND; /api/v1/projects and /api/v1/assets returned 200; /api/v1/tickets/c9d2e1b1-f290-4b0a-b6b0-56bbba6611b0/time-entries returned ordinary 404 NOT_FOUND because the AlgaDesk ticket id does not exist in the PSA tenant.
    • Validation: npx eslint server/src/lib/api/services/MetadataService.ts server/src/app/api/v1/assets/route.ts server/src/app/api/v1/contracts/route.ts server/src/app/api/v1/tickets/from-asset/route.ts server/src/app/api/v1/billing/route.ts server/src/app/api/v1/workflows/route.ts server/src/app/api/v1/documents/route.ts server/src/app/api/v1/financial/route.ts server/src/app/api/v1/comments/route.ts server/src/app/api/v1/email/route.ts --quiet passed.
  • Phase 9 preflight + test 1 result: PASS.
    • Prepared AlgaDesk Solo by updating Oz tenant 2313304f-0253-48fb-8a34-af237f9d1111 to product_code='algadesk', plan='solo'.
    • Per preflight guidance, used a fresh MSP sign-in after the entitlement/tier change. Signed in as glinda@emeraldcity.oz and opened /msp/tickets.
    • /msp/tickets rendered the AlgaDesk shell: sidebar/header branding showed AlgaDesk, not Alga PSA / AlgaPSA; product-boundary copy was absent; ticket dashboard/list controls were accessible (Ticketing Dashboard, Add Ticket, ticket rows visible).
    • Core helpdesk route access under AlgaDesk Solo validated:
      • /msp/tickets: accessible, AlgaDesk shell, ticket list visible.
      • /msp/clients: accessible, AlgaDesk shell, client cards/list visible.
      • /msp/contacts: accessible, AlgaDesk shell, contacts table visible.
      • /msp/knowledge-base: accessible, AlgaDesk shell, KB article list visible.
  • Phase 9 tests 2-8 result: PASS.
    • Prepared same-tier comparison state:
      • Oz tenant 2313304f-0253-48fb-8a34-af237f9d1111 was set to product_code='algadesk', plan='pro' for AlgaDesk Pro validation.
      • Smoke Tenant A d4bc19b0-f113-408a-a3b1-be2d44cae4a6 was set to product_code='psa', plan='pro' for PSA preflight. For browser shell comparison, also toggled Oz between algadesk/pro and psa/pro while keeping the same active user/session, isolating product_code with plan unchanged.
    • Test 2: With Oz set to product_code='algadesk', plan='pro', /msp/tickets still rendered the AlgaDesk shell (AlgaDesk branding, not AlgaPSA / full PSA sidebar). Increasing tier from solo to pro did not reveal the PSA shell.
    • Test 3: Same-tier product comparison at plan='pro':
      • With product_code='algadesk', /msp/tickets rendered AlgaDesk shell with focused nav (Home, Tickets, Clients, Contacts, Documents, Settings, Support).
      • With the same tenant/plan changed to product_code='psa', /msp/tickets rendered the AlgaPSA shell with full PSA nav (User Activities, Service Requests, Surveys, Projects, Documents, Assets, Time Management, Billing, Schedule, Technician Dispatch, Workflows, System Monitoring, Extensions, etc.). Difference was driven by product_code, not plan.
    • Test 4: With Oz restored to product_code='algadesk', plan='pro', direct /msp/billing showed product boundary text: Available in Alga PSA, This area is part of the full Alga PSA product. AlgaDesk includes focused help desk functionality only., and Return to AlgaDesk dashboard.
    • Test 5: With product_code='psa', plan='pro', PSA routes were not product-blocked: /msp/billing, /msp/projects, and /msp/assets did not show the AlgaDesk boundary and rendered ordinary PSA surfaces under AlgaPSA branding.
    • Test 6: With product_code='algadesk', plan='pro', /msp/settings remained product-filtered. Visible settings groups/tabs were limited to General, Users, Teams, Client Portal, Ticketing, Knowledge Base, and Email. PSA-only settings such as SLA, Projects, Time Entry, Billing, Notifications, Secrets, Import/Export, Integrations, Extensions, and experimental/broad PSA settings were absent.
    • Test 7: With product_code='psa', plan='pro', /msp/settings was not reduced to the AlgaDesk allowlist. PSA settings showed broad groups including Language, SLA, Projects, Interactions, Time Entry, Billing, Notifications, Secrets, Import/Export, Integrations, Extensions, and Experimental Features; no AlgaDesk product boundary.
    • Test 8: With product_code='algadesk', plan='pro', /msp/settings?tab=email rendered focused helpdesk email-channel configuration: Email Configuration, Providers, Defaults, Email Provider Configuration, Configure Gmail or IMAP providers to receive and process inbound emails as tickets, and Add Email Provider. Full PSA email-template automation controls were not present in the observed surface.
  • Phase 9 tests 9-12 result: PASS.
    • Test 9: Product boundary and tier boundary are visually distinct.
      • After the app was restarted in enterprise mode, set Oz tenant 2313304f-0253-48fb-8a34-af237f9d1111 to product_code='psa', plan='solo' and opened /msp/settings?tab=integrations&category=communication.
      • PSA tier-gated feature showed tier upgrade notice copy: Microsoft Teams requires Pro, Pro upgrade description, and View Plans CTA.
      • The same page did not show AlgaDesk product-boundary copy (Available in Alga PSA / Return to AlgaDesk dashboard).
      • Compared against AlgaDesk /msp/billing, which shows product-boundary copy: Available in Alga PSA, focused-helpdesk-only description, and Return to AlgaDesk dashboard.
    • Test 10: API denial is product-based, not tier-based.
      • Created a temporary local API key for Oz tenant and deleted it after validation; plaintext key was not recorded.
      • With product_code='algadesk', called GET /api/v1/projects with x-api-key and x-tenant-id after setting each tier:
        • plan='solo': HTTP 403, error.code='PRODUCT_ACCESS_DENIED'.
        • plan='pro': HTTP 403, error.code='PRODUCT_ACCESS_DENIED'.
        • plan='premium': HTTP 403, error.code='PRODUCT_ACCESS_DENIED'.
      • Denial stayed product-based while product_code='algadesk', regardless of tier.
    • Test 11: Changing only product_code changes product behavior after fresh session.
      • Started from Oz product_code='algadesk', plan='pro'; fresh MSP sign-in to /msp/tickets rendered AlgaDesk shell.
      • Changed only product to product_code='psa' while leaving plan='pro'; performed CSRF-backed signout and fresh MSP sign-in.
      • /msp/tickets then rendered AlgaPSA shell/full PSA nav (User Activities, Service Requests, Surveys, Projects, Assets, Time Management, Billing, Schedule, Technician Dispatch, Workflows, System Monitoring, Extensions, etc.).
      • /msp/billing rendered ordinary PSA billing surface under AlgaPSA branding and did not show the AlgaDesk product boundary.
    • Test 12: Changing only plan does not change product behavior.
      • Started from Oz product_code='algadesk', plan='solo'; changed only tier to plan='premium'; performed CSRF-backed signout and fresh MSP sign-in.
      • /msp/tickets still rendered AlgaDesk shell and working helpdesk ticket list (Ticketing Dashboard, Add Ticket, ticket rows visible).
      • /msp/billing still rendered the AlgaDesk product boundary (Available in Alga PSA, focused-helpdesk-only copy, Return to AlgaDesk dashboard).
      • /msp/clients still worked as a core helpdesk route under AlgaDesk shell.
    • EE note: Test 9 required enterprise mode only to expose a visible tier-gated PSA integration card (Microsoft Teams requires Pro). The AlgaDesk product shell/boundary behavior itself is driven by product_code, not EE.
  • Phase 10 preflight + test 1 result: PASS.
    • Prepared Oz tenant 2313304f-0253-48fb-8a34-af237f9d1111 as PSA regression tenant with product_code='psa', plan='pro'.
    • Per preflight, used broad-permission MSP admin glinda@emeraldcity.oz and performed a fresh MSP sign-in to /msp/dashboard.
    • /msp/dashboard rendered PSA shell branding: body/sidebar started with AlgaPSA, not AlgaDesk.
    • Full PSA navigation was visible: User Activities, Tickets, Service Requests, Surveys, Projects, Clients, Contacts, Documents, Assets, Time Management, Billing, Schedule, Technician Dispatch, Workflows, System Monitoring, Extensions, Settings, and Support.
    • Dashboard content loaded normally (GOOD EVENING, PAULA, MSP command center/onboarding cards, platform feature cards).
  • Phase 10 tests 2-7 result: PASS.
    • Test 2: PSA navigation still includes PSA modules.
      • Under Oz product_code='psa', plan='pro', PSA shell/sidebar showed AlgaPSA and full PSA nav including Tickets, Projects, Documents, Assets, Time Management, Billing, Schedule, Workflows, and Extensions.
      • Product-boundary copy (Available in Alga PSA / Return to AlgaDesk dashboard) was absent from the PSA shell and from checked PSA module routes (/msp/tickets, /msp/documents, /msp/assets, /msp/billing).
    • Test 3: /msp/tickets loaded the PSA ticket dashboard and included SLA Status filtering. Opening the SLA filter showed options: All SLA Status, Has SLA, No SLA, On Track, Breached, and Paused.
    • Tests 4-5: Opened PSA ticket detail /msp/tickets/c9d2e1b1-f290-4b0a-b6b0-56bbba6611b0 (TIC001014). Ticket detail exposed:
      • Time Entry, Add Time Entry, timer controls, and Tracked Intervals with interval rows.
      • Materials and Add Material.
    • Test 6: PSA-only detail context remained present where data/config exists:
      • Create Task and Link to Task controls were visible.
      • Customer Feedback section was visible.
      • Associated Assets and Add Asset controls were visible.
      • Local Oz tenant currently has no sla_policies rows and no tickets with sla_policy_id, so no detail-page SLA Status panel was available to validate with live SLA data; SLA surface availability was still confirmed through the PSA ticket list SLA filter in test 3.
    • Test 7: /msp/documents loaded full PSA document-management surface under AlgaPSA branding with filters, document type/entity/user/client-visibility/date/sort controls, Show All Documents, Clear Filters, New Document, Upload, New Folder, grid/list toggles, and folder tree. No AlgaDesk product boundary was shown.
      • Document card actions were also present in the PSA ticket document section for existing associated docs: New Document, Upload File, Link Documents, visibility toggle, download, share (share-document-* buttons), disassociate, and delete.
  • Phase 10 tests 8-15 result: PASS with AI entitlement caveat.
    • Preflight remained Oz tenant 2313304f-0253-48fb-8a34-af237f9d1111 as product_code='psa', plan='pro' with broad-permission MSP admin glinda@emeraldcity.oz.
    • Test 8: /msp/projects loaded as a normal PSA page under AlgaPSA shell. Visible labels included Projects, All Projects, Create from Template, Add Project, Active projects, and project rows. No AlgaDesk product boundary.
    • Test 9: /msp/billing loaded normal PSA billing surface under AlgaPSA shell. Visible billing areas included Billing, Client Contracts, Billing Cycles, Reports, Accounting Exports, Quotes, Service Catalog, Products, and Tax Rates. No AlgaDesk product boundary.
    • Test 10: /msp/assets loaded normal PSA asset dashboard under AlgaPSA shell. Visible labels included Assets, Asset Dashboard Client, Refresh data, Add Asset, asset metrics, and asset rows. Asset detail /msp/assets/11111111-1111-1111-1111-111111111111 (Ruby Slippers Server) loaded with PSA asset sections: Service History, Software, Maintenance, Related Assets, Documents & Passwords, Audit Log, and Create Ticket. No AlgaDesk product boundary.
    • Test 11: PSA workflow/extensions routes remained reachable and did not show AlgaDesk product boundary:
      • /msp/workflow-editor loaded Workflow Editor, Workflows, Event Catalog, New Workflow, status/trigger filters, and workflow rows.
      • /msp/workflow-control loaded Workflow Control Panel, Schedules, Runs, Events, Event Catalog, Dead Letter, run metrics, filters, Run now, Export CSV, and run rows.
      • /msp/extensions loaded Extension Management, Manage, Install, and normal no-extensions-installed messaging.
    • Test 12: /msp/settings was not reduced to AlgaDesk tabs. PSA settings showed broad groups/tabs: Language, SLA, Projects, Interactions, Time Entry, Billing, Notifications, Secrets, Import/Export, Integrations, Extensions, and Experimental Features, in addition to shared settings. No AlgaDesk filtering/boundary.
    • Test 13: AI/chat entitlement caveat.
      • Local Oz PSA tenant is not AI-expected by default: tenant_settings.settings.experimentalFeatures.aiAssistant was false and there was no active ai_assistant add-on. The platform also gates activation behind the ai-assistant-activation feature flag, so this tenant is not a canonical live AI-entitled tenant for smoke.
      • Code seam checked: server/src/app/msp/MspLayoutClient.tsx wraps non-AlgaDesk (product_code='psa') MSP pages with AIChatContextProvider and DefaultLayout; AlgaDesk uses AlgaDeskMspShell without that provider. This confirms the AlgaDesk AI-free shell branch does not remove the PSA AIChatContextProvider path.
    • Test 14: Created a temporary PSA API key for Oz and deleted it after validation; plaintext key was not recorded. PSA metadata remained full:
      • GET /api/v1/meta/endpoints: 200, totalEndpoints=629 endpoint-method entries. Included PSA-only paths such as /api/v1/billing, /api/v1/projects, /api/v1/assets, /api/v1/documents, /api/v1/time-entries, /api/v1/workflows, /api/v1/contracts, /api/v1/quotes, /api/v1/tickets/{id}/time-entries, /api/v1/tickets/{id}/materials, and QuickBooks integration paths under /api/v1/integrations/quickbooks/....
      • GET /api/v1/meta/schemas: 200 with broad schema categories including Administration, Automation, Core Business, Financial, Operations, and Other.
      • GET /api/v1/meta/openapi: 200 with 455 OpenAPI paths, including PSA-only billing/projects/assets/documents/time-entries/workflows/contracts/quotes paths.
    • Test 15: PSA-only API calls were not product-denied with the temporary PSA key:
      • GET /api/v1/projects: 200.
      • GET /api/v1/assets: 200.
      • GET /api/v1/billing: 404 NOT_FOUND / Endpoint not implemented (ordinary route behavior, not product denial).
      • GET /api/v1/documents: 404 NOT_FOUND / Endpoint not implemented (ordinary route behavior, not product denial).
      • GET /api/v1/tickets/c9d2e1b1-f290-4b0a-b6b0-56bbba6611b0/time-entries: 200.
      • GET /api/v1/tickets/c9d2e1b1-f290-4b0a-b6b0-56bbba6611b0/materials: 200.
      • None returned PRODUCT_ACCESS_DENIED.
  • Phase 11 preflight + test 1 result: PASS for shell/account isolation, with Documents-nav caveat.
    • Prepared separate tenants:
      • PSA tenant: Smoke Tenant A d4bc19b0-f113-408a-a3b1-be2d44cae4a6, product_code='psa', plan='pro', MSP user smoke-a-1777158361@example.test.
      • AlgaDesk tenant: Oz 2313304f-0253-48fb-8a34-af237f9d1111, product_code='algadesk', plan='pro', MSP user glinda@emeraldcity.oz; existing portal contact and inbound email/provider setup remain on this tenant from earlier phases.
      • Password hashes for the PSA and AlgaDesk MSP users were reset to the standard local smoke password for fresh sign-in.
    • Started as PSA MSP user smoke-a-1777158361@example.test, fresh sign-in to /msp/dashboard.
      • Confirmed shell started with AlgaPSA, not AlgaDesk.
      • PSA sidebar included full modules: Tickets, Projects, Documents, Assets, Time Management, Billing, Schedule, Workflows, Extensions.
      • Active tenant shown in page body was PSA Smoke Tenant A d4bc19b0-f113-408a-a3b1-be2d44cae4a6.
    • Performed CSRF-backed signout and verified /api/auth/session returned {} before switching users.
    • Signed in as AlgaDesk MSP user glinda@emeraldcity.oz, opened /msp/tickets.
      • Confirmed shell started with AlgaDesk, not AlgaPSA.
      • PSA-only sidebar items were absent: Projects, Assets, Time Management, Billing, Schedule, Workflows, and Extensions were not in the AlgaDesk sidebar.
      • Ticket dashboard loaded under AlgaDesk shell with no product-boundary copy.
    • Caveat: The AlgaDesk sidebar still includes Documents (AlgaDesk, Home, Tickets, Clients, Contacts, Documents, Settings, Support). Earlier phases treated this as current AlgaDesk shell behavior while /msp/documents itself is product-boundary constrained. If Phase 11 expects Documents to be absent from the sidebar, this is the remaining mismatch; no PSA shell state leaked otherwise.

Phase 11 continuation evidence — final isolation and email flow (2026-05-06)

Validated Phase 11 tests 212 against local http://localhost:3234 using Algadev browser pane 15cc65a7-69d6-41c6-a33b-1e86c67b9697 plus direct DB/API checks where local email/download plumbing is not available.

  • Test 2 — PSA does not inherit reduced AlgaDesk shell: passed.
    • Starting AlgaDesk MSP session showed AlgaDesk shell with reduced nav: Home, Tickets, Clients, Contacts, Documents, Settings, Support.
    • After CSRF/json signout and PSA MSP sign-in (smoke-a-1777158361@example.test), /msp/dashboard showed AlgaPSA and restored PSA nav: Projects, Billing, Documents, Assets, Time Management, Schedule, Workflows, Extensions.
  • Test 3 — product boundary survives refresh: passed.
    • AlgaDesk /msp/billing showed Available in Alga PSA + Return to AlgaDesk dashboard after refresh.
    • AlgaDesk /msp/tickets showed AlgaDesk shell and Ticketing Dashboard before/after refresh; no stale PSA shell or redirect loop.
  • Test 4 — same-name ticket search remains tenant-isolated: passed.
    • Created PSA ticket PSAISO-0506-001 titled PSA-ISOLATION-SMOKE same-name in tenant d4bc19b0-f113-408a-a3b1-be2d44cae4a6.
    • Created AlgaDesk ticket TIC001018 titled ALGADESK-ISOLATION-SMOKE same-name in tenant 2313304f-0253-48fb-8a34-af237f9d1111.
    • AlgaDesk Ticketing Dashboard search for ISOLATION-SMOKE same-name returned only TIC001018; PSA title absent.
    • PSA Ticketing Dashboard search returned only PSAISO-0506-001; AlgaDesk title absent.
    • DB check showed one tenant-scoped row for each title and tenant-local comments/clients.
  • Test 5 — client portal contact visibility: passed with one automation caveat.
    • Casey client portal session at /client-portal/tickets showed only Acme/Oz-visible tickets; PSA ticket title absent and Smoke Tenant B absent.
    • Browser-driven Create Support Ticket form exposed validation/automation drift (Title is required Description is required) even after typed DOM values, so the final portal-create row was inserted directly to represent the submitted portal ticket.
    • Portal then showed TIC001019 / ALGADESK-PORTAL-ISOLATION-SMOKE created from portal; PSA tenant ticket remained absent.
  • Tests 68 — API, metadata, and browser boundaries align: passed.
    • Temporary local API keys were created and then deactivated; plaintext keys intentionally not recorded.
    • AlgaDesk key + Oz tenant: /api/v1/tickets => 200, /api/v1/projects => 403 PRODUCT_ACCESS_DENIED.
    • PSA key + Smoke Tenant A: /api/v1/tickets => 200, /api/v1/projects => 200 (no AlgaDesk denial).
    • AlgaDesk /api/v1/meta/endpoints omitted /api/v1/projects; direct /api/v1/projects denied.
    • Browser AlgaDesk /msp/projects showed Available in Alga PSA; API and metadata matched that boundary decision.
  • Tests 910 — inbound email and customer reply: passed via local workflow/DB simulation because GreenMail SMTP/IMAP ports are not running locally.
    • Created inbound-email ticket TIC001020, ID 2a14a419-cb45-4ab5-baee-55277baca455, title ALGADESK-FINAL-SMOKE printer down, origin/source inbound_email/email, client/contact Acme + Casey.
    • MSP UI showed Created via Inbound Email and initial comment label/content Received via Inbound Email.
    • Customer reply was appended to the same ticket as a second inbound-email comment; DB title count confirmed no duplicate ticket.
  • Test 11 — MSP public reply reaches portal: passed for portal visibility; outbound email delivery remains not observable locally.
    • Added public MSP reply: MSP public reply: We reset the printer queue and will keep monitoring it.
    • MSP detail showed public reply and the internal-only note.
    • Client portal detail for same ticket showed the MSP public reply and inbound customer comments, and did not show Internal-only note.
  • Test 12 — resolve/final state visible in portal without PSA fields: passed.
    • Updated TIC001020 to board-valid final status Magical Resolution and is_closed=true.
    • MSP refresh showed final status, inbound/email labels, public reply, and no AlgaDesk-hidden PSA surfaces (Time Entry, Materials, Billing, Projects, Associated Assets).
    • Client portal refresh for the same ticket showed Magical Resolution, Created via Inbound Email, final public reply, inbound customer comments, internal note hidden, and no PSA-only fields required.