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

162 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SCRATCHPAD: Teams Diagnostics + Proactive Test Message
Plan slug: `2026-05-29-teams-diagnostics-test-message`
Created: 2026-05-29
## Why this is the next PR (rationale)
- Observability loop (`2026-05-24-teams-observability-loop`, PR #2562, F001F050) shipped the data substrate: `teams_notification_deliveries`, `teams_audit_events`, `teams_conversation_references`. Nothing *reads* deliveries or conversation references yet.
- Diagnostics + test message is the natural consumer of that substrate, and it's the gating blocker for charging (admin self-verification without logs).
- The test message is built on the **proactive bot path**, which lights up the dormant `teams_conversation_references` table and validates the exact transport Phase 2 channel delivery will reuse — so a cheap Phase 1 win also de-risks Phase 2.
## Key code discoveries (verified)
### Proactive send already exists
- `ee/packages/microsoft-teams/src/lib/teams/bot/teamsBotConnector.ts`
- `sendBotActivity({ serviceUrl, conversationId, replyToId?, activity })` — POSTs to `{serviceUrl}/v3/conversations/{id}/activities`. THE proactive primitive. Already used for synchronous bot replies at `teamsBotHandler.ts:1219`.
- `isBotConnectorConfigured()` / `readBotCredentialsFromEnv()` — creds from env `TEAMS_BOT_APP_ID`, `TEAMS_BOT_APP_TENANT_ID`, `TEAMS_BOT_APP_PASSWORD`.
- `isTrustedServiceUrl()` — suffix allowlist guard, applied inside sendBotActivity.
- Token: client-credentials grant, scope `https://api.botframework.com/.default`, cached.
- Returns `{status:'sent'|'skipped', reason?}`; throws on non-OK HTTP. **Decision:** test message treats `skipped` as a skip outcome and a thrown error as a `failed` delivery row.
- Env vars already wired in `helm/templates/deployment.yaml` and `helm/templates/secret.yaml`.
### Conversation references: written, never read
- `ee/packages/microsoft-teams/src/lib/teams/bot/teamsConversationReferences.ts`
- `upsertTeamsConversationReference(...)` — only writer. Called from `teamsBotHandler.ts:988` on every inbound activity.
- Type `TeamsConversationReferenceType = 'personal' | 'groupChat' | 'channel'`.
- NO reader exists yet → F001 adds it (first consumer of the table).
- Table (`ee/server/migrations/20260524090200_create_teams_conversation_references.cjs`):
- PK `(tenant, microsoft_user_id, conversation_id)`; cols `service_url`, `conversation_type`, `tenant_id_aad`, `channel_id_bot_framework`, `last_activity_at`, timestamps.
- Distributed on `tenant`, colocated with `teams_integrations`.
### Deliveries table — reusable, no migration needed
- `ee/server/migrations/20260524090000_create_teams_notification_deliveries.cjs`
- `category` is **nullable, no CHECK**`category='test'` is safe.
- `status` CHECK ∈ {skipped, sent, delivered, failed}. Test uses skipped/sent/failed.
- `destination_type` free text NOT NULL. Existing real value is `'user_activity'` (`teamsNotificationDelivery.ts:261`). Test uses `'bot_test'`.
- `error_code` CHECK against `DELIVERY_ERROR_CODES` enum — **check whether 'transient'/existing codes cover test failures, or whether error_code should be left NULL for test sends.** (Recorder: `teamsDeliveryRecorder.ts`.)
- UNIQUE `(tenant, idempotency_key)` → include attempt nonce for repeated tests (F012/T017).
- Recorder: `ee/packages/microsoft-teams/src/lib/notifications/teamsDeliveryRecorder.ts` — reuse `recordTeamsNotificationDelivery`-style insert; check its exact input shape (`destinationType`, `category`, `status`, `idempotencyKey`).
### Delivery / mapping helpers
- `teamsNotificationDelivery.ts:321 resolveTeamsRecipientLink(tenant, userId)``{ providerAccountId }` (the Microsoft account-link `provider_account_id`). Used as the Graph `users/{id}` id.
### Diagnostics pattern to mirror
- `shared/services/email/providers/MicrosoftGraphAdapter.ts:745 runMicrosoft365Diagnostics()`:
- `runStep(id, title, fn)` → pushes `{id, title, startedAt, durationMs, status, http?, data?, error?}`; statuses `pass|fail|warn|skip`.
- `recommendations` is a `Set<string>`; report has `createdAt`, `steps[]`, `overallStatus`, recommendations.
- Reference test: `shared/services/email/providers/__tests__/MicrosoftGraphAdapter.diagnostics.test.ts`.
- UI reference: `packages/integrations/src/components/email/admin/Microsoft365DiagnosticsDialog.tsx` (badge rendering pattern for steps).
### Availability / entitlement
- `ee/packages/microsoft-teams/src/lib/teams/teamsAvailability.ts`
- `getTeamsAvailability({isEnterpriseEdition?, tenantId, userId})``{enabled, reason}`; reasons `ce_unavailable | tenant_not_configured | addon_required | enabled`.
- addon check = `tenant_addons` where `addon_key = ADD_ONS.TEAMS` and (`expires_at` null or future).
### Action wiring pattern
- `ee/packages/microsoft-teams/src/lib/actions/integrations/teamsObservabilityActions.ts`: `export const listTeamsDeliveries = withAuth(listTeamsDeliveriesImpl);` (`'use server'`, `@alga-psa/auth/withAuth`). Mirror for `runTeamsDiagnostics` / `sendTeamsTestMessage`.
- Existing teams settings actions: `teamsActions.ts``saveTeamsIntegrationSettingsImpl` (line 225). **Confirm its permission check and reuse the same gate.**
### Settings UI
- `packages/integrations/src/components/settings/integrations/TeamsIntegrationSettings.tsx`
- Uses `Card`/`CardHeader`/`CardContent` from `@alga-psa/ui`. Existing buttons have ids `teams-save-draft`, `teams-reload`, etc. Add new card after the package card (~line 602+).
- `canPersist` style gating already present → reuse for disabling the new buttons (F032).
## Decisions
- **No new migration.** Reuse `teams_notification_deliveries` + `teams_conversation_references`.
- **Test message = proactive bot path** (`sendBotActivity`), NOT a Graph `sendActivityNotification`. This validates the Phase 2 transport and exercises the conversation-reference table.
- `destination_type='bot_test'`, `category='test'` for test delivery rows.
- Test message degrades to a recorded `skipped` row (with reason) for every unhealthy precondition — never silently no-ops.
- Diagnostics is read-only except for the recommendations it computes; no state mutation.
- Incremental settings panel, NOT the full 4-step wizard (that's a later PR).
## Resolved questions
1. **AAD object id vs bot `from.id` — RESOLVED (verified in code 2026-05-29).** The mapping aligns by design:
- Write side: `teamsConversationReferences.ts:48-50` stores `microsoft_user_id = from.aadObjectId || from.id`. For a real Teams user, `aadObjectId` (AAD object id GUID) is populated.
- Link side: `nextAuthOptions.ts:881-882` `extractProviderAccountId` returns `claims.oid` for Microsoft, with an explicit comment (878-883): "oid is the Azure AD object ID — the value Bot Framework sends as activity.from.aadObjectId ... prefer oid". So Microsoft `provider_account_id == oid == aadObjectId == microsoft_user_id`.
- The bot's existing `resolveTeamsLinkedUser.ts:51` already depends on this (`findOAuthAccountLink('microsoft', aadObjectId)`), and it's shipped.
- **Consequence:** drop the planned `resolveTeamsMicrosoftUserId` helper. Just reuse `resolveTeamsRecipientLink(tenant, userId).providerAccountId` (export it; currently file-private in `teamsNotificationDelivery.ts:321`) and key the F001 reader by it. (F003 updated.)
- **Residual edge:** `ssoActions.ts:308` bulk email-backfill sets `provider_account_id = lowerEmail`. Microsoft links created that way won't match a conversation reference — but the bot already can't resolve those users. Diagnostics surfaces this via the F021/F022 warnings; out of scope to fix.
## Open questions (resolve during implementation)
1. **Permission gate** — exact permission enforced by `saveTeamsIntegrationSettingsImpl`; reuse verbatim.
3. **error_code for test failures** — reuse an existing `DELIVERY_ERROR_CODES` value or leave NULL? (CHECK constraint forbids arbitrary codes.)
4. **Where packages/integrations imports EE teams actions** — confirm the existing boundary/registry used so F037 follows it (CE has no EE package; check the stub pattern).
## Commands / runbook
```bash
# Worktree note: this tree is /Users/natalliabukhtsik/Desktop/projects/bigmac (NOT alga-psa).
# Inspect existing pieces
grep -rn "sendBotActivity\|isBotConnectorConfigured" ee/packages/microsoft-teams/src
grep -rn "recordTeamsNotificationDelivery\|TeamsDeliveryDestinationType\|DELIVERY_ERROR_CODES" ee/packages/microsoft-teams/src/lib/notifications
# Rebuild the EE package so server resolves new exports (F038)
# (mirror observability-loop F045 — confirm exact build script for @alga-psa/microsoft-teams)
npm run build -w @alga-psa/microsoft-teams # verify the workspace/script name
# Run the new unit tests (server unit suite location used by prior loop)
# prior loop tests live under server/src/test/unit/internal-notifications/
```
## 2026-05-29 implementation notes
### conversation-reader
- F001: Added `getLatestTeamsConversationReferenceImpl({ tenant, microsoftUserId, conversationType })` in `ee/packages/microsoft-teams/src/lib/teams/bot/teamsConversationReferences.ts`. It scopes through `createTenantKnex`, filters by `tenant`, `microsoft_user_id`, and `conversation_type`, defaults to `personal`, and orders by `last_activity_at DESC`.
- F002: Reader returns a typed `TeamsConversationReferenceRecord | null` and maps missing/blank input, empty result, or read failure to `null` with a warning for read failures.
- F003: Exported existing `resolveTeamsRecipientLink()` and `TeamsRecipientLink` from `teamsNotificationDelivery.ts`; this reuses the verified Microsoft `provider_account_id`/AAD oid as the conversation-reference key.
- F004: No extra barrel change was required for source exports because `ee/packages/microsoft-teams/src/lib/index.ts` already exports both `teamsConversationReferences` and `teamsNotificationDelivery`.
- T001-T004: Extended `server/src/test/unit/lib/teams/bot/teamsConversationReferences.test.ts` to cover newest-row selection, default/explicit conversation-type filtering, tenant scoping, and null return for no match.
- T005-T006: Updated stale checklist wording from the removed `resolveTeamsMicrosoftUserId` helper to `resolveTeamsRecipientLink`, then covered successful Microsoft-link resolution and null when no Microsoft link exists.
### test-message
- F005: Added `sendTeamsTestMessageImpl()` and `sendTeamsTestMessage = withAuth(...)` in `ee/packages/microsoft-teams/src/lib/actions/integrations/teamsDiagnosticsActions.ts`; it uses the same `system_settings:update` gate as `saveTeamsIntegrationSettingsImpl`.
- F006-F010: Implemented precondition skips for inactive add-on, inactive integration, disabled `personal_bot`, missing bot env credentials, missing Microsoft user linkage, and missing personal conversation reference. Each skip records a `teams_notification_deliveries` row before returning.
- F011: Healthy path builds a proactive bot test activity with text/card title `Alga PSA Teams test message` and sends it through `sendBotActivity()` using the stored `serviceUrl` and `conversationId`.
- F012: Extended `writeTeamsDeliveryRow()` to allow `destinationType='bot_test'`, nullable `internalNotificationId`, and an `idempotencyNonce`; test-message rows use `category='test'`, `destination_type='bot_test'`, and a per-click nonce so repeated tests insert distinct rows.
- T007-T018: Added `server/src/test/unit/lib/teams/actions/teamsDiagnosticsActions.test.ts` covering permission denial, every skip reason, happy-path proactive send payload, sent/failed delivery rows, distinct idempotency keys, and tenant-scoped reads/writes.
- Verification: `cd server && npx vitest run src/test/unit/internal-notifications/teamsDeliveryRecorder.test.ts src/test/unit/lib/teams/actions/teamsDiagnosticsActions.test.ts` passed 16 tests.
### diagnostics
- F013-F014: Added `TeamsDiagnosticsStep`, `TeamsDiagnosticsReport`, `TeamsDiagnosticsStatus`, and `runTeamsDiagnosticsImpl()` with ordered `runStep()` capture of status/detail/duration/data/error plus recommendation accumulation.
- F015-F024: Diagnostics now checks Teams add-on availability, integration status, required capabilities, Microsoft profile readiness, package metadata/base URL, bot connector env credentials, current admin Microsoft link, personal conversation reference, and tenant-scoped recent delivery health. Overall status rolls up fail > warn > pass and recommendations are deduped.
- T019-T033: Extended `server/src/test/unit/lib/teams/actions/teamsDiagnosticsActions.test.ts` with diagnostics coverage for permission denial, ordered steps, every required check, recent delivery tenant scoping, status aggregation, recommendation dedupe, and the fully healthy pass case.
- Verification: `cd server && npx vitest run src/test/unit/lib/teams/actions/teamsDiagnosticsActions.test.ts` passed 27 tests; `npm -w @alga-psa/ee-microsoft-teams run typecheck` passed.
### settings-ui
- F025-F032: Added a `Diagnostics & Test Message` card below the package card in `packages/integrations/src/components/settings/integrations/TeamsIntegrationSettings.tsx`. It wires `Run diagnostics` and `Send test message` actions, renders step badges, recommendations, recent delivery summary, mapped test-message results, loading/error state, and disables both buttons unless the integration is active.
- Boundary note: the UI needs the action imports through `packages/integrations/src/actions`; action re-exports and EE action-index export were added while implementing this panel and will be verified/marked in the later `wiring` group.
- T034-T039: Added `TeamsIntegrationSettings.diagnostics.test.tsx` covering step/status rendering, recommendations present/hidden, sent confirmation, missing-conversation-reference guidance, disabled states, and recent delivery summary.
- Verification: `cd server && npx vitest run ../packages/integrations/src/components/settings/integrations/TeamsIntegrationSettings.diagnostics.test.tsx` passed 6 tests (React act warnings emitted by async state updates); `npm -w @alga-psa/integrations run typecheck` passed.
## Out of scope (Phase 2+ — do NOT bundle)
- `teams_channel_mappings`, channel routing/delivery, expanded categories.
- `teams_user_preferences` / quiet hours / mention-only.
- Full setup wizard, configurable channel tab.
- 402/403 entitlement-response standardization.
- Trial flow / metering / billing.
- LLM/fuzzy bot intent, SSO token exchange.
## Suggested commit groups (≈7 commits)
`conversation-reader``test-message``diagnostics``settings-ui``i18n``wiring` → (tests land in each group's commit).
### i18n
- F033-F035: Added `integrations.teams.settings.diagnostics.*` locale keys to every `server/public/locales/*/msp/integrations.json` file. The diagnostics panel now localizes its section title/description, button labels, status labels, errors, test-message reason text, step titles, and recommendation strings with defaultValue fallbacks.
- T040-T041: Added `TeamsIntegrationSettings.i18n.test.ts` to assert every integrations locale contains the diagnostics keys and every test-message skip/fail reason maps to a defined key, preventing raw key leaks or unmapped reason codes.
### wiring
- F036-F037: Verified the EE action barrel exports `teamsDiagnosticsActions`, and `packages/integrations/src/actions` plus `packages/integrations/src/actions/integrations` re-export `runTeamsDiagnostics`, `sendTeamsTestMessage`, and their result types for `TeamsIntegrationSettings`. Added T042 coverage to `teamsActions.test.ts` so the boundary remains importable.
- F038/T043: Rebuilt `@alga-psa/ee-microsoft-teams` with `npm -w @alga-psa/ee-microsoft-teams run build`. Changed the package tsup config to bundle local source into dist entries because the non-bundled dist action barrel re-exported local paths that were not emitted. Verified `dist/actions/index.mjs` contains `runTeamsDiagnostics`, `sendTeamsTestMessage`, `runTeamsDiagnosticsImpl`, and `sendTeamsTestMessageImpl`. Direct Node import still depends on the broader monorepo package runtime state, so the smoke check is a built-file export check after the successful build.
- Verification: `cd server && npx vitest run ../packages/integrations/src/actions/integrations/teamsActions.test.ts`; `npm -w @alga-psa/integrations run typecheck`; `npm -w @alga-psa/ee-microsoft-teams run typecheck`; `npm -w @alga-psa/ee-microsoft-teams run build`.