# Scratchpad — Microsoft Teams Integration V1

- Plan slug: `microsoft-teams-integration-v1`
- Created: `2026-03-07`

## What This Is

Keep a lightweight, continuously-updated log of discoveries and decisions made while implementing this plan.

Prefer short bullets. Append new entries as you learn things, and also *update earlier notes* when a decision changes or an open question is resolved.

## Decisions

- (2026-03-07) V1 scope is MSP users only; client-portal Teams experiences are out of scope.
- (2026-03-07) Teams will use the tenant-owned Microsoft provider configuration model rather than introducing a Teams-only credential store.
- (2026-03-07) Microsoft integration settings should expand from a singleton config into named Microsoft profiles.
- (2026-03-07) Teams will bind to one selected Microsoft profile at tenant admin setup time; end users do not choose a profile.
- (2026-03-07) Notification scope is simplified for v1: personal Teams activity-feed notifications only, no channel/chat routing.
- (2026-03-07) Bot scope is personal-first and command-first, not a general chatbot.
- (2026-03-07) Teams should be treated as one tenant integration with four surfaces, not four separate products.
- (2026-03-07) Shared command/action execution and shared notification payload generation should be reused across Teams surfaces to avoid duplicate implementations.
- (2026-03-07) Microsoft profile migration will be lazy and app-driven instead of a secret-provider-aware SQL migration: the first profile-aware read/write backfills a default profile from legacy tenant secrets and leaves legacy keys in place for compatibility.
- (2026-03-07) Default-profile compatibility will be maintained by mirroring the selected default profile back to legacy tenant secrets (`microsoft_client_id`, `microsoft_client_secret`, `microsoft_tenant_id`) until consumer-binding work lands.
- (2026-03-07) Profile secrets stay out of SQL. The database stores profile metadata plus a deterministic tenant secret reference (`microsoft_profile_<profile_id>_client_secret`).
- (2026-03-07) The Microsoft settings screen should become a profile manager in `Integrations -> Providers`, with one card per profile and shared app-registration guidance rendered inline instead of a separate Teams-only credential editor.
- (2026-03-07) Until explicit consumer-binding records ship, the UI will surface compatibility bindings by treating the active default profile as the current source for Email, Calendar, and MSP SSO.
- (2026-03-07) Teams registration guidance will be generated from deployment base URL plus profile client ID, including tab/bot/message-extension callback URLs and a derived Teams application ID URI (`api://<host>/teams/<clientId>`).
- (2026-03-07) The first Teams setup slice should live in `Integrations -> Providers` next to Microsoft profile management so admins can move between shared Microsoft credential setup and Teams setup without a separate navigation model.
- (2026-03-07) Until the tenant-scoped Teams integration record exists, the Teams settings UI should be a guided readiness card: list Microsoft profiles already ready for Teams and route tenants with no eligible profile back to Microsoft profile management.
- (2026-03-07) Consumer bindings should be lazy and tenant-scoped like Microsoft profile backfill: legacy Microsoft consumers (`email`, `calendar`, `msp_sso`) get compatibility binding rows on first binding-aware access, while `teams` remains explicit-only and never falls back silently.
- (2026-03-07) Teams activity-feed handoffs should reuse existing PSA internal record URLs as the notification source of truth, then derive the Teams personal-tab destination from those URLs instead of introducing a second notification-only entity mapping format.

## Discoveries / Constraints

- (2026-03-07) `packages/integrations/src/components/settings/integrations/MicrosoftIntegrationSettings.tsx` already anchors tenant-owned Microsoft config for Outlook inbound email, Outlook calendar, and MSP SSO; this is the right place to evolve toward named profiles.
- (2026-03-07) `packages/auth/src/lib/nextAuthOptions.ts` already uses Azure AD / Microsoft OAuth and can resolve tenant-specific Microsoft credentials through MSP SSO resolution.
- (2026-03-07) `packages/auth/src/lib/sso/mspSsoResolution.ts` already supports tenant-aware Microsoft provider discovery/resolution for MSP SSO.
- (2026-03-07) Existing in-app notifications already store `link` and metadata, and UI components open specific PSA tickets/tasks/documents from those links.
- (2026-03-07) Existing notification/deep-link infrastructure can be reused for Teams notification payloads instead of building a second record-linking system.
- (2026-03-07) Existing event/workflow infrastructure is a better trigger source for Teams notifications than adding a Teams-specific event bus.
- (2026-03-07) Existing extension iframe surfaces are useful precedent for embedded UI and auth forwarding, but they are not a native substitute for Teams bot/message-extension surfaces.
- (2026-03-07) Existing Microsoft integration flows for email, calendar, and Entra already establish tenant-secret and Graph API patterns that Teams can reuse.
- (2026-03-07) Teams package generation now persists `app_id`, `bot_id`, and a summarized `package_metadata` blob on `teams_integrations`, which keeps setup/status views tenant-local and avoids recomputing storage-less manifest state.
- (2026-03-07) Rebinding Teams to a different Microsoft profile must clear persisted package metadata and reset non-`not_configured` install state back to `install_pending` so stale package/install assumptions do not survive profile swaps.
- (2026-03-07) The Teams setup UI now has a package handoff card that only enables generation for saved/non-`not_configured` setups, which avoids generating install artifacts from unsaved or invalid profile selections.
- (2026-03-07) Teams manifest generation and future action/notification deep links now share the same `TEAMS_PERSONAL_TAB_ENTITY_ID` and Teams tab deep-link builder, which reduces drift between package declarations and runtime navigation targets.
- (2026-03-07) `packages/auth/src/lib/nextAuthOptions.ts` now exposes a Teams-specific `buildTeamsAuthOptions(tenantId)` entry point that resolves Microsoft credentials from the tenant-selected Teams profile and intentionally bypasses generic app/global Microsoft fallback for that surface.
- (2026-03-07) There was no runtime `/teams/tab` page yet even though the generated Teams manifest already pointed the personal tab there.
- (2026-03-07) `server/src/middleware.ts` still does not protect `/teams/*`, so the Teams tab route has to resolve auth state itself and redirect unauthenticated users into the existing MSP sign-in flow.
- (2026-03-07) The auth package needed to export `resolveTeamsMicrosoftProviderConfig` through `packages/auth/src/lib/sso/index.ts` so new Teams server surfaces can reuse the tenant-selected Microsoft profile resolver without unsupported deep imports.
- (2026-03-07) The Teams auth callback URIs surfaced in Microsoft profile guidance (`/api/teams/auth/callback/tab|bot|message-extension`) were still missing, and `/api/teams/auth/*` would have been blocked by middleware API-key enforcement until explicitly exempted.
- (2026-03-07) The existing SSO account-link registry already stores tenant-scoped Microsoft provider account IDs, so Teams user mapping can reuse `findOAuthAccountLink('microsoft', providerAccountId)` plus the `users` table instead of introducing a separate Teams linkage table in the first identity slice.
- (2026-03-07) Teams runtime entry points can receive a Microsoft/Teams tenant claim hint (`microsoftTenantId`, `teamsTenantId`, or `tid`) even before richer Teams SDK bootstrap lands, so the shared auth resolver can reject requests whose Teams tenant context does not match the tenant-selected Microsoft profile.
- (2026-03-07) Generated Teams personal-tab deep links already encode destination context as a `context` JSON payload, so the runtime `/teams/tab` entry point needs to understand that payload rather than only flat query params.
- (2026-03-07) Teams tab and callback entry points were independently constructing MSP sign-in redirects; a shared Teams reauth URL helper is safer because it guarantees consistent callback preservation and gives Teams flows an explicit `teamsReauth=1` marker instead of surfacing generic auth failures.
- (2026-03-07) Teams entry points can arrive with `tenant=<slug>` rather than a raw tenant UUID. The shared Teams auth resolver needs to translate tenant slugs to tenant IDs before enforcing same-tenant checks, otherwise valid vanity-host or slug-based deep links are rejected.
- (2026-03-07) Teams auth state already re-resolves the selected Teams Microsoft profile on every request instead of caching profile identity in the session, so a Teams profile rebind can invalidate stale Microsoft-tenant assumptions immediately as long as the request still carries the old tenant hint.
- (2026-03-07) Existing Microsoft consumers still read the legacy tenant-secret keys directly. Introducing profiles without default-secret mirroring would break Outlook email, Outlook calendar, and MSP SSO compatibility.
- (2026-03-07) `packages/integrations/src/components/settings/integrations/IntegrationsSettingsPage.tsx` already gives the right long-term home for Microsoft profile management: `Integrations -> Providers`, not a new top-level settings tab.
- (2026-03-07) The backend compatibility guard is sufficient for `F015`/`T029-T030`: archiving the default profile is blocked until another profile is made default, which preserves the active compatibility binding.
- (2026-03-07) The second slice is complete: `F019-F035` and `T037-T070` pass with the profile manager UI, per-profile readiness/rendering, inline registration guidance, refresh, default switching, and archive confirmation.
- (2026-03-07) There was no existing Teams admin settings surface in the repo; `packages/integrations/src/components/settings/integrations/IntegrationsSettingsPage.tsx` `Providers` is the right mount point for the first Teams setup card.
- (2026-03-07) The shared `Button` component does not expose custom ids reliably in the jsdom contract harness, so the Teams settings contract test uses role/name queries for refresh actions.
- (2026-03-07) The third slice is complete: `F036`, `F048`, `F056`, `F057`, `F058` and `T071`, `T072`, `T095`, `T096`, `T111`, `T112`, `T113`, `T114`, `T115`, `T116` pass with a Providers-mounted Teams setup card, hash-based navigation between Microsoft profiles and Teams setup, and guided remediation when no eligible profile exists.
- (2026-03-07) `server/migrations/20260307143000_create_microsoft_profile_consumer_bindings.cjs` adds a tenant-scoped one-row-per-consumer binding table keyed by `(tenant, consumer_type)` with composite FK back to `microsoft_profiles`.
- (2026-03-07) `packages/integrations/src/actions/integrations/microsoftActions.ts` now exposes binding-aware helpers/actions: `listMicrosoftConsumerBindings`, `setMicrosoftConsumerBinding`, and `resolveMicrosoftProfileForConsumer`.
- (2026-03-07) The fourth slice is complete: `F037`, `F038`, `F039`, `F040`, `F041` and `T073`, `T074`, `T075`, `T076`, `T077`, `T078`, `T079`, `T080`, `T081`, `T082` pass with a real Microsoft consumer-binding model, lazy compatibility binding backfill for legacy Microsoft consumers, and Teams explicit-binding resolution.
- (2026-03-07) `server/migrations/20260307153000_create_teams_integrations.cjs` adds the tenant-scoped Teams integration table keyed by `tenant`, linked to a selected Microsoft profile, and storing install status, enabled capabilities, notification categories, and allowed quick actions.
- (2026-03-07) `packages/integrations/src/actions/integrations/teamsActions.ts` now exposes `getTeamsIntegrationStatus` and `saveTeamsIntegrationSettings` with tenant-admin gating, client-user rejection, explicit selected-profile validation, and readiness checks before activation.
- (2026-03-07) The fifth slice is complete: `F042`, `F043`, `F044`, `F045`, `F046`, `F047` and `T083`, `T084`, `T085`, `T086`, `T087`, `T088`, `T089`, `T090`, `T091`, `T092`, `T093`, `T094` pass with a real Teams integration record plus guarded admin save/load actions.
- (2026-03-07) `packages/integrations/src/components/settings/integrations/TeamsIntegrationSettings.tsx` now renders a real Teams setup form with explicit profile selection, readiness checklisting, selected-profile app-registration guidance, capability toggles, notification preferences, and activate/deactivate actions.
- (2026-03-07) The sixth slice is complete: `F049`, `F050`, `F051`, `F052`, `F053`, `F054`, `F055` and `T097`, `T098`, `T099`, `T100`, `T101`, `T102`, `T103`, `T104`, `T105`, `T106`, `T107`, `T108`, `T109`, `T110` pass with a saveable Teams setup workflow in the settings UI.
- (2026-03-07) `packages/integrations/src/actions/integrations/teamsPackageActions.ts` now builds tenant-specific Teams app package metadata from the selected Teams profile, including manifest structure, personal tab/bot declarations, compose-extension commands, activity-feed activity types, webApplicationInfo, valid domains, and environment-aware base URLs.
- (2026-03-07) The seventh slice is complete: `F059`, `F060`, `F061`, `F062`, `F063`, `F064`, `F065`, `F066`, `F067`, `F068`, `F069`, `F071` and `T117`, `T118`, `T119`, `T120`, `T121`, `T122`, `T123`, `T124`, `T125`, `T126`, `T127`, `T128`, `T129`, `T130`, `T131`, `T132`, `T133`, `T134`, `T135`, `T136`, `T137`, `T138`, `T141`, `T142` pass with a generated Teams manifest/package model and guarded package-status retrieval.

## Commands / Runbooks

- (2026-03-07) Scaffolded plan folder with: `python3 /Users/roberisaacs/.codex/skills/alga-plan/scripts/scaffold_plan.py "Microsoft Teams Integration V1" --slug microsoft-teams-integration-v1`
- (2026-03-07) Backend verification commands:
  - `cd server && npx vitest run --config vitest.config.ts ../packages/integrations/src/actions/integrations/microsoftActions.test.ts ../packages/integrations/src/actions/integrations/providerReadiness.test.ts`
  - `pnpm --dir packages/integrations typecheck`
- (2026-03-07) UI verification commands:
  - `cd server && npx vitest run --config vitest.config.ts ../packages/integrations/src/components/settings/integrations/MicrosoftIntegrationSettings.contract.test.tsx`
  - `cd server && npx vitest run --config vitest.config.ts ../packages/integrations/src/actions/integrations/microsoftActions.test.ts ../packages/integrations/src/actions/integrations/providerReadiness.test.ts ../packages/integrations/src/components/settings/integrations/MicrosoftIntegrationSettings.contract.test.tsx`
  - `cd server && npx vitest run --config vitest.config.ts ../packages/integrations/src/components/settings/integrations/TeamsIntegrationSettings.contract.test.tsx ../packages/integrations/src/components/settings/integrations/IntegrationsSettingsPage.providers.test.ts`
  - `cd server && npx vitest run --config vitest.config.ts ../packages/integrations/src/components/settings/integrations/MicrosoftIntegrationSettings.contract.test.tsx ../packages/integrations/src/components/settings/integrations/TeamsIntegrationSettings.contract.test.tsx ../packages/integrations/src/components/settings/integrations/IntegrationsSettingsPage.providers.test.ts`
  - `cd server && npx vitest run --config vitest.config.ts ../packages/integrations/src/actions/integrations/microsoftActions.test.ts ../packages/integrations/src/actions/integrations/microsoftConsumerBindings.test.ts ../server/src/test/unit/migrations/microsoftConsumerBindingsMigration.test.ts`
  - `cd server && npx vitest run --config vitest.config.ts ../packages/integrations/src/actions/integrations/teamsActions.test.ts ../server/src/test/unit/migrations/teamsIntegrationsMigration.test.ts`
  - `cd server && npx vitest run --config vitest.config.ts ../packages/integrations/src/components/settings/integrations/TeamsIntegrationSettings.contract.test.tsx ../packages/integrations/src/components/settings/integrations/IntegrationsSettingsPage.providers.test.ts`
  - `cd server && npx vitest run --config vitest.config.ts ../packages/integrations/src/actions/integrations/teamsPackageActions.test.ts`
- (2026-03-07) Relevant local references:
  - `packages/integrations/src/components/settings/integrations/MicrosoftIntegrationSettings.tsx`
  - `packages/integrations/src/components/settings/integrations/MicrosoftIntegrationSettings.contract.test.tsx`
  - `packages/integrations/src/components/settings/integrations/TeamsIntegrationSettings.tsx`
  - `packages/integrations/src/components/settings/integrations/TeamsIntegrationSettings.contract.test.tsx`
  - `packages/integrations/src/components/settings/integrations/IntegrationsSettingsPage.tsx`
  - `packages/integrations/src/components/settings/integrations/IntegrationsSettingsPage.providers.test.ts`
  - `packages/integrations/src/actions/integrations/microsoftActions.ts`
  - `packages/integrations/src/actions/integrations/microsoftConsumerBindings.test.ts`
  - `packages/integrations/src/actions/integrations/providerReadiness.ts`
  - `packages/integrations/src/actions/integrations/teamsActions.ts`
  - `packages/integrations/src/actions/integrations/teamsActions.test.ts`
  - `packages/integrations/src/actions/integrations/teamsPackageActions.ts`
  - `packages/integrations/src/actions/integrations/teamsPackageActions.test.ts`
  - `packages/auth/src/lib/nextAuthOptions.ts`
  - `packages/auth/src/lib/sso/mspSsoResolution.ts`
  - `packages/notifications/src/actions/internal-notification-actions/internalNotificationActions.ts`
  - `server/src/lib/utils/notificationLinkResolver.ts`
  - `server/src/lib/eventBus`
- (2026-03-07) Verified Teams package metadata persistence and invalidation with:
  - `pnpm --dir packages/integrations typecheck`
  - `cd server && npx vitest run --config vitest.config.ts ../packages/integrations/src/actions/integrations/teamsActions.test.ts ../packages/integrations/src/actions/integrations/teamsPackageActions.test.ts ../server/src/test/unit/migrations/teamsPackageMetadataMigration.test.ts`
- (2026-03-07) Verified Teams tab auth bootstrap with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/resolveTeamsTabAuthState.test.ts src/test/unit/app/teams/tab/page.test.tsx`
  - `pnpm --dir packages/auth typecheck`
- (2026-03-07) Verified Teams auth callback routes with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/resolveTeamsTabAuthState.test.ts src/test/unit/app/teams/tab/page.test.tsx src/app/api/teams/auth/callback/bot/route.test.ts src/app/api/teams/auth/callback/message-extension/route.test.ts`
- (2026-03-07) Verified Teams linked-user resolution with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/resolveTeamsLinkedUser.test.ts src/test/unit/lib/teams/resolveTeamsTabAuthState.test.ts src/test/unit/app/teams/tab/page.test.tsx src/app/api/teams/auth/callback/bot/route.test.ts src/app/api/teams/auth/callback/message-extension/route.test.ts`
- (2026-03-07) Verified Teams Microsoft-tenant mismatch rejection with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/resolveTeamsTabAuthState.test.ts src/test/unit/app/teams/tab/page.test.tsx src/app/api/teams/auth/callback/bot/route.test.ts src/app/api/teams/auth/callback/message-extension/route.test.ts src/test/unit/lib/teams/resolveTeamsLinkedUser.test.ts`
- (2026-03-07) Verified Teams deep-link destination bootstrap with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/resolveTeamsTabDestination.test.ts src/test/unit/app/teams/tab/page.test.tsx src/test/unit/lib/teams/resolveTeamsTabAuthState.test.ts src/app/api/teams/auth/callback/bot/route.test.ts src/app/api/teams/auth/callback/message-extension/route.test.ts src/test/unit/lib/teams/resolveTeamsLinkedUser.test.ts`
- (2026-03-07) Verified Teams-safe reauthentication redirects with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/buildTeamsReauthUrl.test.ts src/test/unit/lib/teams/resolveTeamsTabDestination.test.ts src/test/unit/app/teams/tab/page.test.tsx src/test/unit/lib/teams/resolveTeamsTabAuthState.test.ts src/app/api/teams/auth/callback/bot/route.test.ts src/app/api/teams/auth/callback/message-extension/route.test.ts src/test/unit/lib/teams/resolveTeamsLinkedUser.test.ts`
- (2026-03-07) Verified Teams tenant-slug resolution with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/resolveTeamsTabAuthState.test.ts src/test/unit/app/teams/tab/page.test.tsx src/app/api/teams/auth/callback/bot/route.test.ts src/app/api/teams/auth/callback/message-extension/route.test.ts src/test/unit/lib/teams/buildTeamsReauthUrl.test.ts src/test/unit/lib/teams/resolveTeamsTabDestination.test.ts src/test/unit/lib/teams/resolveTeamsLinkedUser.test.ts`
- (2026-03-07) Verified Teams profile-rebind invalidation with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/resolveTeamsTabAuthState.test.ts`
- (2026-03-07) Verified Teams tab destination access gating and state distinction with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/app/teams/tab/page.test.tsx src/app/api/teams/auth/callback/bot/route.test.ts src/test/unit/lib/teams/resolveTeamsTabAccessState.test.ts src/test/unit/lib/teams/resolveTeamsTabAuthState.test.ts`
- (2026-03-07) Verified Teams selected-profile precedence over broad Microsoft env credentials with:
  - `cd packages/auth && npx vitest run --config vitest.config.ts src/lib/sso/teamsMicrosoftProviderResolution.test.ts`
- (2026-03-07) Verified Teams-safe auth remediation copy with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/resolveTeamsTabAuthState.test.ts`
- (2026-03-07) Verified Teams personal-tab default landing and fallback routing with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/app/teams/tab/page.test.tsx src/test/unit/lib/teams/resolveTeamsTabDestination.test.ts`
- (2026-03-07) Verified Teams activity-feed notification deep-link routing with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/resolveTeamsTabDestination.test.ts src/test/unit/app/teams/tab/page.test.tsx`
  - `cd server && npx vitest run --config vitest.config.ts ../packages/integrations/src/actions/integrations/teamsPackageActions.test.ts`
  - `pnpm --dir packages/integrations typecheck`

## Progress Log

- (2026-03-07) Completed `F070` by adding a migration for `teams_integrations.app_id`, `bot_id`, and `package_metadata`, and by persisting generated Teams package metadata in `packages/integrations/src/actions/integrations/teamsPackageActions.ts`.
- (2026-03-07) Completed `F072` by clearing stored Teams package metadata and resetting install readiness to `install_pending` when the selected Teams Microsoft profile changes in `packages/integrations/src/actions/integrations/teamsActions.ts`.
- (2026-03-07) Completed `T139`, `T140`, `T143`, and `T144` with focused migration and action-layer tests covering package metadata storage and stale-profile invalidation behavior.
- (2026-03-07) Completed `F073` by adding a Teams package handoff panel in `packages/integrations/src/components/settings/integrations/TeamsIntegrationSettings.tsx` that prepares tenant package metadata and downloads a manifest JSON snapshot for admin install handoff.
- (2026-03-07) Completed `T145` and `T146` with UI contract coverage for successful package handoff generation/download and recoverable package-generation failures.
- (2026-03-07) Completed `F074` by adding shared Teams personal-tab deep-link builders and template targets in `packages/integrations/src/actions/integrations/teamsPackageActions.ts`, keeping package declarations aligned with future notification/action destinations.
- (2026-03-07) Completed `T147` and `T148` with package-action tests covering deep-link template generation and prerequisite guard behavior.
- (2026-03-07) Completed `F075` by adding `packages/auth/src/lib/sso/teamsMicrosoftProviderResolution.ts` and wiring `buildTeamsAuthOptions(tenantId)` in `packages/auth/src/lib/nextAuthOptions.ts` so Teams-specific auth can use the selected Teams Microsoft profile instead of broad fallback credentials.
- (2026-03-07) Completed `T149` and `T150` with focused resolver tests plus a NextAuth contract test covering the Teams-specific auth-options entry point.
- (2026-03-07) Completed `F076` by adding `server/src/lib/teams/resolveTeamsTabAuthState.ts` plus the first runtime `/teams/tab` page in `server/src/app/teams/tab/page.tsx`, which resolves MSP tenant/user context, redirects unauthenticated users to `/auth/msp/signin`, and renders Teams-safe remediation for non-ready tenants.
- (2026-03-07) Completed `T151` and `T152` with focused Teams-tab auth-state tests in `server/src/test/unit/lib/teams/resolveTeamsTabAuthState.test.ts` and page-wiring coverage in `server/src/test/unit/app/teams/tab/page.test.tsx`.
- (2026-03-07) Completed `F077` and `F078` by adding shared Teams auth callback handling in `server/src/lib/teams/handleTeamsAuthCallback.ts` plus concrete `/api/teams/auth/callback/bot` and `/api/teams/auth/callback/message-extension` routes that resolve tenant/MSP user context from the existing Teams auth-state resolver and return Teams-safe popup payloads.
- (2026-03-07) Added `/api/teams/auth/` to the middleware API-key skip list so Teams browser-based auth callbacks can complete without being rejected as generic API traffic.
- (2026-03-07) Completed `T153`, `T154`, `T155`, and `T156` with route-level tests covering ready callback payloads, unauthenticated redirect behavior, and safe rejected-access payloads for both bot and message-extension surfaces.
- (2026-03-07) Completed `F079`, `F080`, `F081`, and `F082` via the shared Teams auth-state resolver: only internal MSP users can proceed, client users are rejected explicitly, the resolver prefers `getSessionWithRevocationCheck()` with the existing auth fallback path, and missing/not-ready Teams profiles return remediation-safe `not_configured` or `invalid_profile` states.
- (2026-03-07) Completed `T157`, `T158`, `T159`, `T160`, `T161`, `T162`, `T163`, and `T164` by extending the Teams auth-state unit coverage to assert MSP-only acceptance, client-user rejection, revocation-checked session reuse, and admin-readable not-configured/invalid-profile failures.
- (2026-03-07) Completed `F083` by adding `server/src/lib/teams/resolveTeamsLinkedUser.ts`, which resolves a tenant-scoped Microsoft provider account link back to the matching internal PSA user and rejects missing, cross-tenant, or client-user mappings safely.
- (2026-03-07) Completed `T165` and `T166` with focused coverage in `server/src/test/unit/lib/teams/resolveTeamsLinkedUser.test.ts` for linked-user resolution plus missing-identity, cross-tenant, and client-user rejection paths.
- (2026-03-07) Completed `F084` by extending `server/src/lib/teams/resolveTeamsTabAuthState.ts` to compare incoming Teams Microsoft-tenant hints against the tenant-selected Teams Microsoft profile and reject mismatches centrally.
- (2026-03-07) Completed `T167` and `T168` by extending Teams auth-state, tab-page, and auth-callback tests to cover matching tenant claims and wrong-Microsoft-tenant rejection behavior.
- (2026-03-07) Completed `F085` by adding `server/src/lib/teams/resolveTeamsTabDestination.ts` and wiring `/teams/tab` to bootstrap destinations from Teams `context` payloads while preserving the exact deep-link payload through MSP sign-in redirects.
- (2026-03-07) Completed `T169` and `T170` with focused destination-parser coverage and Teams-tab page tests covering deep-link bootstrap plus safe rejection/redirect behavior for protected deep-link entries.
- (2026-03-07) Completed `F086` by adding `server/src/lib/teams/buildTeamsReauthUrl.ts` and routing both Teams tab and Teams auth callbacks through the same explicit Teams-safe MSP reauthentication path.
- (2026-03-07) Completed `T171` and `T172` with focused reauth URL helper coverage plus tab/bot callback tests asserting expired or invalid Teams sessions redirect to Teams-safe reauth URLs instead of leaking raw auth failures.
- (2026-03-07) Completed `F087` by extending `server/src/lib/teams/resolveTeamsTabAuthState.ts` to resolve tenant slugs via `getTenantIdBySlug` before comparing Teams entry-point tenant hints to the authenticated MSP tenant.
- (2026-03-07) Completed `T173` and `T174` with Teams auth-state, Teams tab page, and bot callback tests covering slug-based tenant resolution on vanity-host-style entry points plus safe rejection when the slug resolves to the wrong tenant.
- (2026-03-07) Completed `F088` by verifying that `server/src/lib/teams/resolveTeamsTabAuthState.ts` re-reads Teams profile binding state per request, so stale Microsoft-tenant assumptions are rejected immediately after a Teams profile rebind.
- (2026-03-07) Completed `T175` and `T176` with a focused Teams auth-state regression test that simulates a tenant profile rebind between requests and asserts the old Teams Microsoft tenant hint is rejected on the next request.
- (2026-03-07) Completed `F089` by adding `server/src/lib/teams/resolveTeamsTabAccessState.ts` and wiring `server/src/app/teams/tab/page.tsx` through it so a ready Teams SSO session still has to pass existing PSA permission checks plus tenant-scoped entity lookup before ticket, project-task, contact, time-entry, or approval destinations are treated as accessible.
- (2026-03-07) Completed `T177` and `T178` with focused access-resolver and Teams-tab page tests covering allowed destination access, permission-denied short-circuiting, tenant-scoped not-found handling, and Teams-safe fallback rendering after authentication succeeds.
- (2026-03-07) Completed `F090` by verifying the existing `packages/auth/src/lib/sso/teamsMicrosoftProviderResolution.ts` implementation already resolves Teams auth exclusively from the tenant-selected Teams Microsoft profile instead of broad app/global Microsoft environment credentials.
- (2026-03-07) Completed `T179` and `T180` with explicit resolver regression tests asserting Teams ignores broad Microsoft env credentials both when a valid selected profile exists and when Teams setup/profile state is missing or invalid.
- (2026-03-07) Completed `F091` by verifying the Teams tab page and Teams bot auth callback keep `unauthenticated`, `forbidden`, and `not_configured` outcomes distinct at the surface boundary instead of collapsing them into one generic failure path.
- (2026-03-07) Completed `T181` and `T182` with surface-level tests covering not-configured Teams tab rendering and not-configured bot auth callback payloads alongside the existing unauthenticated redirect and forbidden-access cases.
- (2026-03-07) Completed `F092` by verifying the shared Teams auth-state resolver only returns human-readable remediation copy that remains safe to surface inside Teams tab/callback UI constraints.
- (2026-03-07) Completed `T183` and `T184` with a focused auth-state test asserting unauthenticated, client-user, and invalid-profile failures use safe remediation text and avoid raw OAuth/provider jargon.
- (2026-03-07) Completed `F093` by verifying the existing `/teams/tab` route acts as the Teams personal-tab entry point for PSA and can render a ready default destination without additional Teams context.
- (2026-03-07) Completed `T185` and `T187` with a Teams-tab page test asserting the default ready state renders the `my_work` destination for the personal-tab entry path.
- (2026-03-07) Completed `F094` by verifying the default Teams tab destination model already resolves to `my_work`, which matches the intended PSA technician landing surface for the personal tab.
- (2026-03-07) Completed `T186` and `T188` with destination-parser coverage asserting unsupported or malformed Teams context falls back safely to `my_work` instead of failing open.
- (2026-03-07) Completed `F095`, `F096`, `F097`, `F098`, and `F099` by expanding the Teams tab destination model and page shell so ticket, project-task, approval, time-entry, and contact deep links all render explicit entity context, with contact links carrying optional client context from Teams message-driven entry points.
- (2026-03-07) Completed `T189` through `T200` with parser and page coverage for valid ticket/project-task/approval/time-entry/contact deep links plus guard coverage for incomplete context payloads falling back safely to `my_work`.
- (2026-03-07) Completed `F100` by changing destination copy from generic bootstrap text to entity-specific titles and summaries so the initial Teams tab load visibly confirms the intended PSA entity.
- (2026-03-07) Completed `F101` by changing post-auth record-access failures from a dead-end error card into a safe `my_work` fallback shell with explanatory messaging about the unavailable requested destination.
- (2026-03-07) Completed `T201` and `T202` with page tests asserting unavailable ticket/project-task/approval/time-entry/contact links land on the `my_work` fallback shell and preserve the requested-destination context in user-facing remediation copy.
- (2026-03-07) Completed `F102` by adding a shared Teams full-PSA URL builder and surfacing an `Open in full PSA` handoff from the tab shell for ticket, project-task, approval, time-entry, and contact destinations, including fallback states that still preserve the originally requested record URL.
- (2026-03-07) Completed `T203` and `T204` with a focused URL-builder test plus page assertions covering visible full-PSA handoff links for entity destinations and omission of that link for the default `my_work` landing.
- (2026-03-07) Completed `F103` by reconciling the plan with the existing access-state gate: the Teams tab only renders a destination after PSA permission checks and tenant-scoped entity resolution succeed, and otherwise falls back safely instead of rendering unauthorized content.
- (2026-03-07) Completed `T205` and `T206` against the existing `resolveTeamsTabAccessState` happy/guard coverage and the tab-page fallback tests that already exercise authorization-preserving behavior for ready versus unavailable destinations.
- (2026-03-07) Completed `F104` by replacing the ready-state Teams record shell with a same-origin iframe of the existing PSA record route when an authorized entity destination is available, so the tab now reuses PSA screens/components instead of keeping a parallel Teams-only detail view.
- (2026-03-07) Completed `T207` and `T208` with page assertions proving authorized entity destinations render an embedded PSA route while fallback states intentionally suppress that embed and stay on the safe Teams shell.
- (2026-03-07) Completed `F105` by keeping the embedded PSA composition on same-origin internal MSP routes that inherit the authenticated MSP session and tenant-scoped host context, avoiding any second auth callback or token-bearing bootstrap channel inside the iframe URL.
- (2026-03-07) Completed `T209` and `T210` with URL-builder tests asserting embedded PSA routes remain relative `/msp/...` paths without callback/token parameters plus fallback-shell tests that suppress the embed when the requested destination is unavailable.
- (2026-03-07) Completed `F106` by teaching Teams personal-tab routing and Teams deep-link helpers to derive supported tab destinations from existing PSA notification URLs (`/msp/tickets/...`, `/msp/projects/...?...`, `/msp/time-sheet-approvals?...`, `/msp/time-entry?...`, `/msp/contacts/...`) so activity-feed handoffs can target the exact tab destination without a second notification-specific mapping layer.
- (2026-03-07) Completed `T211` and `T212` with unit and page coverage for notification-link destination parsing, Teams deep-link generation from PSA URLs, and safe my-work fallback when a notification targets an unsupported or unavailable record.
  - `server/migrations/20260307120000_create_microsoft_profiles.cjs`
  - `server/migrations/20260307143000_create_microsoft_profile_consumer_bindings.cjs`
  - `server/migrations/20260307153000_create_teams_integrations.cjs`
  - `server/src/test/unit/migrations/microsoftConsumerBindingsMigration.test.ts`
  - `server/src/test/unit/migrations/teamsIntegrationsMigration.test.ts`
- (2026-03-07) The focused vitest slice still emits pre-existing React `act(...)` warnings from `MicrosoftIntegrationSettings.contract.test.tsx`; the tests pass, but the harness remains noisy.
- (2026-03-07) The Teams setup contract tests currently emit similar non-blocking React `act(...)` warnings while asserting async save flows; the tests pass, but the harness remains noisy.
- (2026-03-07) `npx vitest run --config vitest.config.ts src/test/unit/app/teams/tab/page.test.tsx src/test/unit/lib/teams/resolveTeamsTabDestination.test.ts`
- (2026-03-07) `npx vitest run --config vitest.config.ts src/test/unit/app/teams/tab/page.test.tsx src/test/unit/lib/teams/resolveTeamsTabDestination.test.ts src/test/unit/lib/teams/buildTeamsFullPsaUrl.test.ts`
- (2026-03-07) `npx vitest run --config vitest.config.ts src/test/unit/app/teams/tab/page.test.tsx`
- (2026-03-07) `npx vitest run --config vitest.config.ts src/test/unit/app/teams/tab/page.test.tsx src/test/unit/lib/teams/buildTeamsFullPsaUrl.test.ts`
- (2026-03-07) Verified bot-result, message-extension-result, cold-start tab, and escalation-path behavior with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/resolveTeamsTabAccessState.test.ts src/test/unit/lib/teams/resolveTeamsTabDestination.test.ts src/test/unit/app/teams/tab/page.test.tsx ../packages/integrations/src/actions/integrations/teamsPackageActions.test.ts`
  - `pnpm --dir packages/integrations typecheck`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-07) Running multiple coverage-enabled Vitest commands in parallel can race on `server/coverage/.tmp`; rerun sequentially for a clean green verification slice.
- (2026-03-07) Next unchecked feature after this slice is `F111` (Shared Teams action registry exists for bot commands, message-extension actions, and card/dialog submits).

## Links / References

- Microsoft Teams docs used during investigation:
  - Tabs overview: `https://learn.microsoft.com/en-us/microsoftteams/platform/tabs/what-are-tabs`
  - Tab SSO overview: `https://learn.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/authentication/tab-sso-overview`
  - Tab SSO manifest: `https://learn.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/authentication/tab-sso-manifest`
  - Bot SSO overview: `https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/authentication/bot-sso-overview`
  - Bot SSO manifest: `https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/authentication/bot-sso-manifest`
  - Activity feed notifications: `https://learn.microsoft.com/en-us/microsoftteams/platform/tabs/send-activity-feed-notification`
  - Deep links: `https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/deep-links`
  - Search commands: `https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/search-commands/define-search-command`
  - Action commands: `https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/action-commands/define-action-command`
  - Dialogs / task modules: `https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/task-modules/invoking-task-modules`
- Existing related plans:
  - `ee/docs/plans/2026-02-23-msp-tenant-first-sso-provider-resolution`
  - `ee/docs/plans/2026-02-20-entra-integration-phase-1`

## Open Questions

- Do existing Microsoft consumers beyond Teams need explicit per-consumer profile selection UX in v1, or is a default-binding compatibility path sufficient initially?
- How much approval behavior belongs in Teams quick actions versus deep-linking into the tab?
- What exact tenant-by-tenant Teams app packaging and distribution flow should be used across local/dev/staging/prod?

## Additional Discoveries / Constraints

- (2026-03-07) The existing Teams tab runtime already understood direct `context` and notification-style PSA URLs; bot and message-extension result handoff only needed explicit surface-specific link aliases plus stable deep-link builders, not a new tab route.
- (2026-03-07) The personal tab shell is a good place to surface invoking-surface context (`bot`, `message_extension`, `notification`) because it confirms why the user landed on a record without changing destination authorization semantics.
- (2026-03-07) The project-task Teams access check should stay local to Teams and use `ProjectService.getTasks(projectId, context)` for project/task consistency instead of widening the shared `IProjectTask` interface with a `project_id` field that ripples through unrelated project-editing call sites.
- (2026-03-07) The smallest reusable Teams action-layer pattern in this repo is a typed registry plus normalized DTO/result unions in `server/src/lib/teams/actions`, not the heavier workflow runtime registry; that keeps bot, message-extension, and quick-action semantics aligned with the existing Teams auth/setup code.
- (2026-03-07) Teams action results can derive Teams-tab links from existing PSA record URLs by flowing through `buildTeamsFullPsaUrl` first and only then calling the Teams package deep-link builders, which keeps the PSA route as the source of truth.
- (2026-03-07) The first idempotency slice for Teams actions is process-local and keyed by `tenant + user + action + idempotencyKey`; that is enough to prevent duplicate mutation execution within the current app process while keeping the action layer free of new persistence until a later concurrency hardening pass is justified.
- (2026-03-07) The first Teams bot transport can stay SDK-free: a Next.js route can accept Bot Framework-style activity JSON, resolve tenant/user context locally, and map shared Teams action results into simple hero-card responses without introducing `botbuilder`.
- (2026-03-07) Bot requests need a tenant-resolution helper separate from browser SSO because webhook activities arrive without a PSA session; the tenant can be resolved from an explicit query hint first and then from the selected Teams profile's Microsoft tenant ID.
- (2026-03-07) Bot follow-up shortcuts should be filtered through `listAvailableTeamsActions(...)` so ticket result cards only advertise tenant-enabled actions and stay aligned with central Teams capability gating.
- (2026-03-07) The smallest useful bot mutation syntax is `assign ticket <ticket-id> to <assignee>` with `to me` as the default fallback and `add note <ticket-id>: <note>` for note entry; both shapes keep parsing deterministic without introducing multi-step bot state.
- (2026-03-07) Resolving a non-self assignee from the bot should require `user:read` permission and only match active internal PSA users by exact user ID, email, username, or full name so Teams assignment stays tenant-scoped and predictable.
- (2026-03-07) Pending-approval lookup needs to mirror the existing Manager Approval dashboard scope logic instead of `TimeSheetService.list(...)`, because the service layer does not currently apply manager / reports-to visibility for time-sheet approvals.
- (2026-03-07) The current time-sheet approval model supports `approve` and `request_changes`, not a distinct reject mutation, so the Teams bot treats `reject approval ...` as a user-friendly alias for the shared `request_changes` action.
- (2026-03-07) The first Teams message-extension slice can stay SDK-free like the bot transport: a Next.js invoke route can accept `composeExtension/query` payloads directly and reuse tenant/user resolution plus the shared Teams action layer for result cards.
- (2026-03-07) Approval lookup needed a reusable helper outside the action registry so both bot approvals and message-extension search can share the same manager/reports-to visibility rules and optional query filtering.
- (2026-03-07) The smallest safe v1 message-extension search surface is one `searchRecords` command for `compose` and `commandBox` contexts that aggregates tickets, tasks, contacts, and approvals into shared `open_record` hero cards.
- (2026-03-07) The first message-context action slice can reuse the same message-extension route by handling `composeExtension/fetchTask` and `composeExtension/submitAction`, returning Teams-safe task responses without introducing a second webhook surface.
- (2026-03-07) The current message-action transport is intentionally a handoff foundation: it preserves Teams-authenticated message context and shows a task-module preview while later slices add actual message-to-ticket and message-to-update mutations.
- (2026-03-07) Teams message-extension pagination can use native `queryOptions.skip/count` windowing instead of a custom continuation token; for the merged multi-entity result set, fetching up to `skip + count` per entity and slicing centrally is sufficient for the first paged slice.
- (2026-03-07) The cleanest way to surface message-extension quick actions is to reuse `listAvailableTeamsActions(...)` per search hit and append only currently available mutation titles to the shared `open_record` card summary, which keeps result cards aligned with tenant allowed-action settings without introducing premature action buttons.
- (2026-03-07) The first inline Teams message-to-ticket slice can stay inside `teamsMessageExtensionHandler.ts` as long as it still reuses the shared ticket model for creation, tenant default board/status/priority configuration, and shared `open_record` link mapping for confirmation.
- (2026-03-07) Storing Teams message provenance plus the submit idempotency key in `tickets.attributes` is the smallest migration-free way to satisfy message-source auditability and duplicate-submit replay for Teams message actions.
- (2026-03-07) The first message-to-update slice can stay in `server/src/lib/teams/messageExtension/teamsMessageExtensionHandler.ts` as long as ticket mutations still flow through the shared Teams action registry and project-task updates fail safely into a Teams-tab handoff instead of inventing a second task-specific mutation path.
- (2026-03-07) Ticket message-to-update provenance is simplest when stored on the created `comments.metadata` payload, so the shared `TicketService.addComment(...)` contract now needs to accept optional metadata for both internal notes and customer-visible replies.
- (2026-03-07) A generic `Continue in Teams tab` action can be attached to message-action task modules whenever `teams_integrations` already has `appId` plus `packageMetadata.baseUrl`; if those install artifacts are missing, the task flow should remain functional without the handoff button.
- (2026-03-07) `createTicketFromMessage` and `updateFromMessage` task-module UIs can stay in `teamsMessageExtensionHandler.ts`, but their submit paths should route through dedicated registry actions (`create_ticket_from_message`, `update_from_message`) so message-driven workflows share the same idempotency, permission, and result-link plumbing as the rest of Teams.
- (2026-03-07) The cleanest Teams notification slice is a second delivery channel hanging off `packages/notifications/src/realtime/internalNotificationBroadcaster.ts`, so existing internal notification producers and workflow-triggered notification rows do not need Teams-specific fanout logic.
- (2026-03-07) Teams activity-feed delivery can reuse the existing `internal_notifications.link` PSA URL by converting it with `buildTeamsPersonalTabDeepLinkFromPsaUrl(...)`; no notification-specific destination mapping table is needed.
- (2026-03-07) Category routing for Teams notifications is easiest to derive from the notification row itself: template names cover assignment/customer-reply/SLA, while approval requests can be recognized from approval-style PSA links.
- (2026-03-07) Delivering personal Teams activity-feed notifications requires the Teams app manifest to request the `TeamsActivity.Send.User` application RSC permission in addition to the existing activity declaration.

## Progress Log

- (2026-03-07) Completed `F107` and `F108` by teaching `server/src/lib/teams/resolveTeamsTabDestination.ts` to resolve `botResultLink` and `messageExtensionResultLink` entries, adding explicit entry-source handling in `server/src/app/teams/tab/page.tsx`, and exposing surface-specific Teams deep-link helpers in `packages/integrations/src/actions/integrations/teamsPackageActions.ts`.
- (2026-03-07) Completed `F109` by refining the not-configured Teams tab copy so cold-start tenants get a setup-specific heading and remediation message instead of a generic unavailable state.
- (2026-03-07) Completed `F110` by making the tab shell explicitly describe the `Open in full PSA` handoff as the escalation path when a workflow needs more context than Teams cards or quick actions provide.
- (2026-03-07) Completed `T213`, `T214`, `T215`, and `T216` with destination-parser, page-shell, and deep-link-builder coverage for bot-result and message-extension-result tab handoff plus safe fallback behavior.
- (2026-03-07) Completed `T217`, `T218`, `T219`, and `T220` with page-shell coverage for graceful cold-start setup messaging and explicit full-PSA escalation copy.
- (2026-03-07) Fixed two pre-existing server TypeScript blockers encountered during verification by narrowing `normalizeTenantClaim` to accept `undefined` in `server/src/lib/teams/resolveTeamsTabAuthState.ts` and by keeping the project-task Teams access check local in `server/src/lib/teams/resolveTeamsTabAccessState.ts` instead of widening shared project-task interfaces.
- (2026-03-07) Completed `F111` through `F126` by adding `server/src/lib/teams/actions/teamsActionRegistry.ts`, which centralizes Teams action definitions, input normalization, target resolution, permission/capability gating, result mapping, allowed-action metadata, and idempotent mutation replay for shared bot/message-extension/quick-action execution.
- (2026-03-07) Completed `T221` through `T252` with `server/src/test/unit/lib/teams/actions/teamsActionRegistry.test.ts`, covering registry metadata, input normalization, ticket/task/contact/approval/time-entry resolution, permission guards, consistent validation errors, invoking-surface metadata, PSA-first deep-link generation, idempotent mutation replay, partial-failure remediation, capability gating, and service reuse for time entry and approval mutations.
- (2026-03-07) Exported `getTeamsIntegrationExecutionState(tenant)` from `packages/integrations/src/actions/integrations/teamsActions.ts` so future Teams route handlers can read install status, enabled capabilities, allowed actions, app id, and package metadata without duplicating Teams integration row mapping.
- (2026-03-07) Completed `F127` through `F134` by adding an SDK-free Teams bot transport in `server/src/app/api/teams/bot/messages/route.ts` and `server/src/lib/teams/bot/teamsBotHandler.ts`, plus `server/src/lib/teams/resolveTeamsTenantContext.ts` for tenant selection from Teams webhook context. The handler enforces personal-scope-only behavior, resolves linked MSP users, renders help/unsupported guidance, parses supported bot commands, and filters follow-up shortcuts through tenant allowed-action state.
- (2026-03-07) Completed `F135`, `F136`, and `F137` by wiring the bot `my tickets` command through the shared Teams action layer and rendering summary cards with ticket number/title/status-style summaries plus Teams-tab and full-PSA links.
- (2026-03-07) Completed `F138` and `F139` by wiring `ticket <id>` through the shared Teams action layer, enriching open-record summaries with entity-specific detail, and returning clear recoverable not-found/permission failures inside bot-safe cards.
- (2026-03-07) Completed `T253` through `T278` with `server/src/test/unit/lib/teams/bot/teamsBotHandler.test.ts` and `server/src/app/api/teams/bot/messages/route.test.ts`, covering route presence, invalid JSON handling, welcome/help responses, unsupported-command recovery, personal-scope-only enforcement, allowed-action filtering, shared entity-reference parsing, `my tickets` cards/deep links, and `ticket <id>` success/error behavior.

- (2026-03-07) Verified shared Teams action-layer execution with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/actions/teamsActionRegistry.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
  - `pnpm --dir packages/integrations typecheck`
- (2026-03-07) Verified Teams bot foundation and lookup commands with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/bot/teamsBotHandler.test.ts src/app/api/teams/bot/messages/route.test.ts src/test/unit/lib/teams/actions/teamsActionRegistry.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-07) Verified Teams bot mutation commands with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/bot/teamsBotHandler.test.ts src/app/api/teams/bot/messages/route.test.ts src/test/unit/lib/teams/actions/teamsActionRegistry.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-07) Verified Teams bot approval commands with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/actions/teamsActionRegistry.test.ts src/test/unit/lib/teams/bot/teamsBotHandler.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-07) Verified Teams message-extension search with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/actions/teamsActionRegistry.test.ts src/test/unit/lib/teams/messageExtension/teamsMessageExtensionHandler.test.ts src/app/api/teams/message-extension/query/route.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-07) Verified Teams message-extension action transport and focused command definitions with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/messageExtension/teamsMessageExtensionHandler.test.ts src/app/api/teams/message-extension/query/route.test.ts ../packages/integrations/src/actions/integrations/teamsPackageActions.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
  - `pnpm --dir packages/integrations typecheck`
- (2026-03-07) Verified Teams message-extension pagination with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/messageExtension/teamsMessageExtensionHandler.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-07) Verified Teams message-extension quick-action surfacing with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/messageExtension/teamsMessageExtensionHandler.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-07) Verified Teams message-extension shared surface metadata and PSA-first search access patterns with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/messageExtension/teamsMessageExtensionHandler.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-07) Verified Teams message-to-ticket task-card creation, inline submit, message metadata persistence, and duplicate-submit replay with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/messageExtension/teamsMessageExtensionHandler.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`

- (2026-03-07) Completed `F140`, `F141`, `F142`, and `F143` by teaching `server/src/lib/teams/bot/teamsBotHandler.ts` to parse `assign ticket` commands, default `assign ticket <ticket-id>` to self-assignment, resolve other assignees from active internal users with explicit `user:read` gating, and return the shared action result with an assignee-specific success summary plus Teams-tab/full-PSA links.
- (2026-03-07) Completed `F144`, `F145`, and `F146` by teaching the same bot handler to parse `add note <ticket-id>: <note>`, preserve ticket context when the note body is missing, execute the shared `add_note` Teams action, and return the saved-note confirmation with follow-up deep links.
- (2026-03-07) Completed `F147`, `F148`, and `F149` by extending the bot handler with `reply to contact <ticket-id>: <reply>`, preserving ticket context when only the ticket reference is present, executing the shared `reply_to_contact` action, and returning the customer-reply confirmation with Teams-tab/full-PSA links.
- (2026-03-07) Completed `F150`, `F151`, and `F152` by extending the bot handler with `log time ticket|task <id> <duration>: <note>`, parsing minute/hour durations into the shared `log_time` action input, defaulting the time entry to the trailing duration ending now, and supporting both ticket and project-task targets with the same shared mutation path.
- (2026-03-07) Completed `T279` through `T286` with focused bot-handler tests covering assignment success, assignee lookup, missing-ticket validation, user-read permission failures during assignee resolution, and safe not-found mutation results for bot action handoff.
- (2026-03-07) Completed `T287` through `T292` with bot-handler tests covering note-command parsing, missing-note remediation that preserves ticket context, shared `add_note` execution, and successful follow-up link rendering after note creation.
- (2026-03-07) Completed `T293` through `T298` with bot-handler tests covering reply-command parsing, missing-reply remediation, shared `reply_to_contact` execution, and successful follow-up link rendering after a customer-visible reply is created.
- (2026-03-07) Completed `T299` through `T304` with bot-handler tests covering missing work-item/duration remediation, shared ticket/task `log_time` execution, duration-and-note normalization, and follow-up link rendering after Teams time entry creation.
- (2026-03-07) Completed `F153` by adding a shared `my_approvals` Teams action plus bot support for `my approvals`, reusing the Manager Approval dashboard scope rules so the bot lists only approval items the signed-in approver can review.
- (2026-03-07) Completed `F154` by extending the bot parser/UX with `approve approval <id>` and `request changes` / `reject approval <id>: <comment>` commands that execute the shared `approval_response` action and return Teams-tab/full-PSA follow-up links.
- (2026-03-07) Completed `T305` and `T306` with registry + bot coverage for pending-approval lookup, permission failures, approval summaries, and approval-result shortcut rendering.
- (2026-03-07) Completed `T307` and `T308` with bot-handler coverage for approval approval/request-changes execution plus missing-reference and missing-comment remediation.
- (2026-03-07) Completed `F155`, `F156`, `F157`, `F158`, `F159`, and `F160` by adding the SDK-free Teams message-extension route in `server/src/app/api/teams/message-extension/query/route.ts` plus `server/src/lib/teams/messageExtension/teamsMessageExtensionHandler.ts`, which resolves tenant and linked-user context, accepts `searchRecords` queries from compose/command-box contexts, and returns ticket/task/contact/approval hero-card results through the shared `open_record` Teams action mapping.
- (2026-03-07) Completed `T309` through `T320` with `server/src/app/api/teams/message-extension/query/route.test.ts` and `server/src/test/unit/lib/teams/messageExtension/teamsMessageExtensionHandler.test.ts`, covering route delegation, invalid JSON handling, tenant/user-gated search aggregation, permission-based suppression, empty-state recovery, and compose/command-box context enforcement.
- (2026-03-07) Completed `F161` by extending `server/src/lib/teams/messageExtension/teamsMessageExtensionHandler.ts` to accept `composeExtension/fetchTask` and `composeExtension/submitAction` requests for `createTicketFromMessage` and `updateFromMessage`, returning Teams-safe task-module and task-message responses from message context.
- (2026-03-07) Completed `F162`, `F163`, `F165`, `F166`, `F168`, `F169`, `F171`, `F172`, `F173`, `F174`, `F175`, and `F176` by tightening the message-extension transport around tenant-scoped/user-scoped search execution, compact shared-result card rendering, Teams-tab/full-PSA deep links, inactive-tenant remediation, focused command definitions, and shared `open_record` result mapping.
- (2026-03-07) Completed `T321` through `T352` with focused handler/package coverage for message-context action requests, tenant/user resolution, permission-scoped search behavior, compact result cards, deep-link buttons, inactive-tenant remediation, focused command definitions, and shared result mapping.
- (2026-03-07) Completed `F164` by teaching `server/src/lib/teams/messageExtension/teamsMessageExtensionHandler.ts` to honor Teams `queryOptions.skip/count` search windows and validate pagination input before returning a paged attachment list.
- (2026-03-07) Completed `T327` and `T328` with handler coverage for paged ticket result windows plus recoverable invalid-pagination messaging.
- (2026-03-07) Completed `F167` and `F170` by extending message-extension search cards to append tenant-allowed mutation titles from `listAvailableTeamsActions(...)`, so search results surface only currently supported quick actions for the current entity and tenant.
- (2026-03-07) Completed `T333`, `T334`, `T339`, and `T340` with handler coverage for available-action summaries plus suppression of unavailable tenant-disabled actions.
- (2026-03-07) Completed `F177` and `F178` by asserting message-extension search continues to invoke the shared Teams action layer with `surface: 'message_extension'` while reusing existing PSA ticket, contact, task, and approval access paths instead of introducing a Teams-only index.
- (2026-03-07) Completed `T353`, `T354`, `T355`, and `T356` with handler coverage for shared invoking-surface metadata plus explicit assertions against `TicketService.search`, `ContactService.search`, tenant task queries, and `listPendingApprovalsForTeams(...)`.
- (2026-03-07) Completed `F179`, `F180`, and `F181` by extending `server/src/lib/teams/messageExtension/teamsMessageExtensionHandler.ts` so `createTicketFromMessage` now returns a real adaptive-card task form with captured message subject/body context, tenant-backed board/status/client/contact choices, and default-backed ticket field handling for inline ticket creation.
- (2026-03-07) Completed `F182`, `F183`, and `F184` by executing inline ticket creation through the shared ticket model, storing Teams source-message/idempotency metadata in `tickets.attributes`, replaying duplicate submits safely by that idempotency key, and reusing shared `open_record` result links for post-create Teams-tab/full-PSA confirmation.
- (2026-03-07) Completed `T357` through `T368` with handler coverage for create-ticket task-card rendering, permission/setup guards, inline ticket creation, source-metadata persistence, confirmation deep links, invalid input recovery, and duplicate-submit replay.
- (2026-03-07) Completed `F185`, `F186`, `F187`, `F188`, and `F189` by extending `server/src/lib/teams/messageExtension/teamsMessageExtensionHandler.ts` so `updateFromMessage` now opens a real adaptive-card task form, accepts ticket/project-task targets, routes ticket note/reply mutations through the shared Teams action registry, and persists Teams message provenance into `comments.metadata`.
- (2026-03-07) Completed `F190`, `F191`, `F192`, `F193`, `F194`, and `F195` by returning shared Teams-tab/full-PSA confirmation links after successful update actions, preserving permission/setup failures as recoverable task responses, prefilling update content from the selected Teams message, and offering explicit Teams-tab handoff paths for richer create/update workflows.
- (2026-03-07) Completed `T369` through `T390` with message-extension handler coverage for update task-card rendering, ticket/task targeting, internal-note and customer-reply execution, provenance metadata flow, recoverable validation/permission failures, and richer Teams-tab handoff behavior.
- (2026-03-07) Verified Teams message-to-update task-card rendering, shared ticket mutations, provenance metadata flow, and Teams-tab handoff behavior with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/messageExtension/teamsMessageExtensionHandler.test.ts`
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/actions/teamsActionRegistry.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-07) Completed `F196` by moving both message-driven submit paths onto dedicated shared registry actions in `server/src/lib/teams/actions/teamsActionRegistry.ts`, leaving the message-extension handler responsible only for task-module UI, field prefilling, and final task-response rendering.
- (2026-03-07) Completed `T391` and `T392` with handler coverage proving `createTicketFromMessage` and `updateFromMessage` submits delegate to the shared action layer and still surface recoverable Teams-safe failures when the shared action rejects the request.
- (2026-03-07) Verified shared message-driven action delegation with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/messageExtension/teamsMessageExtensionHandler.test.ts src/test/unit/lib/teams/actions/teamsActionRegistry.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-07) The shared quick-action slice can stay transport-thin: `server/src/lib/teams/quickActions/teamsQuickActionHandler.ts` owns adaptive-card/task-module rendering and submit handling, while the route only parses JSON and delegates into the shared Teams action registry plus `open_record` handoff links.
- (2026-03-07) Completed `F197` through `F210` by adding `server/src/lib/teams/quickActions/teamsQuickActionHandler.ts` and `server/src/app/api/teams/quick-actions/route.ts`, which render minimal adaptive-card/task-module forms for assign-ticket, add-note, reply-to-contact, log-time, and approval-response flows; prefill from Teams message/entity context; validate required inputs before submit; execute the shared Teams action layer on submit; return Teams-safe success/error/cancel responses; suppress unavailable actions centrally; and hand users off to the Teams tab when the richer record workflow is the safer path.
- (2026-03-07) Completed `T393` through `T420` with `server/src/app/api/teams/quick-actions/route.test.ts` and `server/src/test/unit/lib/teams/quickActions/teamsQuickActionHandler.test.ts`, covering route delegation, invalid JSON handling, per-action form rendering, contextual prefills, tenant/user/action gating, shared-action submission, success and recoverable failure responses, cancel/dismiss behavior, allowed-action visibility, and Teams-tab handoff for unavailable or too-complex quick actions.
- (2026-03-07) Verified Teams quick-action adaptive-card flows with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/lib/teams/quickActions/teamsQuickActionHandler.test.ts src/app/api/teams/quick-actions/route.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-07) Verified Teams notification delivery channel, Graph payload mapping, and manifest permission handoff with:
  - `cd server && npx vitest run --config vitest.config.ts src/test/unit/internal-notifications/teamsNotificationDelivery.test.ts src/test/unit/internal-notifications/internalNotificationBroadcaster.test.ts ../packages/integrations/src/actions/integrations/teamsPackageActions.test.ts`
  - `pnpm --dir packages/notifications typecheck`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-07) Next unchecked feature after this slice is `F231` (Permissions: only authorized tenant admins can manage Microsoft profiles and Teams setup).

- (2026-03-07) Completed `F211` through `F230` by adding `packages/notifications/src/realtime/teamsNotificationDelivery.ts` and refactoring `packages/notifications/src/realtime/internalNotificationBroadcaster.ts` so every internal notification can attempt an independent Teams activity-feed delivery path alongside Redis in-app delivery, gated by active Teams setup, personal-notification capability/category settings, linked Microsoft accounts, and tenant-selected Teams profile credentials.
- (2026-03-07) Completed `T421` through `T460` with `server/src/test/unit/internal-notifications/teamsNotificationDelivery.test.ts`, `server/src/test/unit/internal-notifications/internalNotificationBroadcaster.test.ts`, and updated `packages/integrations/src/actions/integrations/teamsPackageActions.test.ts`, covering category classification, Teams deep-link reuse, recipient linkage, sent/delivered/failed workflow events, safe suppression on disabled/missing prerequisites, channel independence from Redis delivery, and the required Teams manifest permission for activity-feed sends.
- (2026-03-07) Archiving a Microsoft profile only checked the default-profile compatibility guard. It did not yet block the case where Teams explicitly selected a non-default profile, and there was no backend delete action for applying the same guard to deletions.
- (2026-03-07) Completed `F231` through `F248` by adding binding-aware Microsoft profile lifecycle guards in `packages/integrations/src/actions/integrations/microsoftActions.ts`, exporting a new `deleteMicrosoftProfile` action, and re-verifying the existing Teams surface authorization/fallback behavior that was already present across Teams tab, bot, message extension, quick-action, and profile-compatibility code.
- (2026-03-07) Completed `T461` through `T496` with updated integrations action tests plus re-verification of the existing Teams surface suites, covering tenant-admin gating for Microsoft/Teams settings, cross-surface authorization enforcement, mutation denial for read-only users, tenant-scoped search/lookup behavior, legacy Microsoft compatibility APIs/bindings, Teams-profile rebind safety, Teams-selected profile archive/delete guards, client-user rejection, guided not-configured remediation, inactive-integration action suppression, and tenant allowed-action enforcement across bot and message extension.
- (2026-03-07) Verified Teams permission/compatibility/fallback regression coverage with:
  - `cd server && npx vitest run --config vitest.config.ts ../packages/integrations/src/actions/integrations/microsoftActions.test.ts ../packages/integrations/src/actions/integrations/microsoftConsumerBindings.test.ts ../packages/integrations/src/actions/integrations/teamsActions.test.ts src/test/unit/lib/teams/actions/teamsActionRegistry.test.ts src/test/unit/lib/teams/resolveTeamsTabAccessState.test.ts src/test/unit/lib/teams/resolveTeamsTabAuthState.test.ts src/test/unit/lib/teams/bot/teamsBotHandler.test.ts src/test/unit/lib/teams/messageExtension/teamsMessageExtensionHandler.test.ts src/test/unit/lib/teams/quickActions/teamsQuickActionHandler.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
  - `pnpm --dir packages/integrations typecheck`
- (2026-03-07) Completed `T497` through `T516` by re-verifying the existing focused suites that already exercise the end-to-end acceptance paths for migration compatibility, Teams package identity, SSO tab entry, notification deep-link routing, bot lookup/mutation/approval commands, message-extension search and message-driven actions, quick-action submit flows, Teams notification delivery/suppression, and cross-surface unauthorized/client-user rejection.
- (2026-03-07) Verified Teams acceptance-scenario coverage with:
  - `cd server && npx vitest run --config vitest.config.ts ../packages/integrations/src/actions/integrations/microsoftActions.test.ts ../packages/integrations/src/actions/integrations/microsoftConsumerBindings.test.ts ../packages/integrations/src/actions/integrations/teamsActions.test.ts ../packages/integrations/src/actions/integrations/teamsPackageActions.test.ts src/test/unit/lib/teams/resolveTeamsTabAuthState.test.ts src/test/unit/lib/teams/resolveTeamsTabAccessState.test.ts src/test/unit/lib/teams/resolveTeamsTabDestination.test.ts src/test/unit/app/teams/tab/page.test.tsx src/test/unit/lib/teams/actions/teamsActionRegistry.test.ts src/test/unit/lib/teams/bot/teamsBotHandler.test.ts src/app/api/teams/bot/messages/route.test.ts src/test/unit/lib/teams/messageExtension/teamsMessageExtensionHandler.test.ts src/app/api/teams/message-extension/query/route.test.ts src/test/unit/lib/teams/quickActions/teamsQuickActionHandler.test.ts src/app/api/teams/quick-actions/route.test.ts src/test/unit/internal-notifications/teamsNotificationDelivery.test.ts src/test/unit/internal-notifications/internalNotificationBroadcaster.test.ts`
  - `pnpm --dir server exec tsc -p tsconfig.json --noEmit --pretty false`
  - `pnpm --dir packages/integrations typecheck`
  - `pnpm --dir packages/notifications typecheck`
- (2026-03-07) No unchecked feature or test items remain in the Microsoft Teams Integration V1 plan.
