Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
16 KiB
16 KiB
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, F001–F050) 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_referencestable 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.tssendBotActivity({ serviceUrl, conversationId, replyToId?, activity })— POSTs to{serviceUrl}/v3/conversations/{id}/activities. THE proactive primitive. Already used for synchronous bot replies atteamsBotHandler.ts:1219.isBotConnectorConfigured()/readBotCredentialsFromEnv()— creds from envTEAMS_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 treatsskippedas a skip outcome and a thrown error as afaileddelivery row.
- Env vars already wired in
helm/templates/deployment.yamlandhelm/templates/secret.yaml.
Conversation references: written, never read
ee/packages/microsoft-teams/src/lib/teams/bot/teamsConversationReferences.tsupsertTeamsConversationReference(...)— only writer. Called fromteamsBotHandler.ts:988on 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); colsservice_url,conversation_type,tenant_id_aad,channel_id_bot_framework,last_activity_at, timestamps. - Distributed on
tenant, colocated withteams_integrations.
- PK
Deliveries table — reusable, no migration needed
ee/server/migrations/20260524090000_create_teams_notification_deliveries.cjscategoryis nullable, no CHECK →category='test'is safe.statusCHECK ∈ {skipped, sent, delivered, failed}. Test uses skipped/sent/failed.destination_typefree text NOT NULL. Existing real value is'user_activity'(teamsNotificationDelivery.ts:261). Test uses'bot_test'.error_codeCHECK againstDELIVERY_ERROR_CODESenum — 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— reuserecordTeamsNotificationDelivery-style insert; check its exact input shape (destinationType,category,status,idempotencyKey).
Delivery / mapping helpers
teamsNotificationDelivery.ts:321 resolveTeamsRecipientLink(tenant, userId)→{ providerAccountId }(the Microsoft account-linkprovider_account_id). Used as the Graphusers/{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?}; statusespass|fail|warn|skip.recommendationsis aSet<string>; report hascreatedAt,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.tsgetTeamsAvailability({isEnterpriseEdition?, tenantId, userId})→{enabled, reason}; reasonsce_unavailable | tenant_not_configured | addon_required | enabled.- addon check =
tenant_addonswhereaddon_key = ADD_ONS.TEAMSand (expires_atnull 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 forrunTeamsDiagnostics/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/CardContentfrom@alga-psa/ui. Existing buttons have idsteams-save-draft,teams-reload, etc. Add new card after the package card (~line 602+). canPersiststyle gating already present → reuse for disabling the new buttons (F032).
- Uses
Decisions
- No new migration. Reuse
teams_notification_deliveries+teams_conversation_references. - Test message = proactive bot path (
sendBotActivity), NOT a GraphsendActivityNotification. 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
skippedrow (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
- AAD object id vs bot
from.id— RESOLVED (verified in code 2026-05-29). The mapping aligns by design:- Write side:
teamsConversationReferences.ts:48-50storesmicrosoft_user_id = from.aadObjectId || from.id. For a real Teams user,aadObjectId(AAD object id GUID) is populated. - Link side:
nextAuthOptions.ts:881-882extractProviderAccountIdreturnsclaims.oidfor 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 Microsoftprovider_account_id == oid == aadObjectId == microsoft_user_id. - The bot's existing
resolveTeamsLinkedUser.ts:51already depends on this (findOAuthAccountLink('microsoft', aadObjectId)), and it's shipped. - Consequence: drop the planned
resolveTeamsMicrosoftUserIdhelper. Just reuseresolveTeamsRecipientLink(tenant, userId).providerAccountId(export it; currently file-private inteamsNotificationDelivery.ts:321) and key the F001 reader by it. (F003 updated.) - Residual edge:
ssoActions.ts:308bulk email-backfill setsprovider_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.
- Write side:
Open questions (resolve during implementation)
- Permission gate — exact permission enforced by
saveTeamsIntegrationSettingsImpl; reuse verbatim. - error_code for test failures — reuse an existing
DELIVERY_ERROR_CODESvalue or leave NULL? (CHECK constraint forbids arbitrary codes.) - 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
# 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 })inee/packages/microsoft-teams/src/lib/teams/bot/teamsConversationReferences.ts. It scopes throughcreateTenantKnex, filters bytenant,microsoft_user_id, andconversation_type, defaults topersonal, and orders bylast_activity_at DESC. - F002: Reader returns a typed
TeamsConversationReferenceRecord | nulland maps missing/blank input, empty result, or read failure tonullwith a warning for read failures. - F003: Exported existing
resolveTeamsRecipientLink()andTeamsRecipientLinkfromteamsNotificationDelivery.ts; this reuses the verified Microsoftprovider_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.tsalready exports bothteamsConversationReferencesandteamsNotificationDelivery. - T001-T004: Extended
server/src/test/unit/lib/teams/bot/teamsConversationReferences.test.tsto 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
resolveTeamsMicrosoftUserIdhelper toresolveTeamsRecipientLink, then covered successful Microsoft-link resolution and null when no Microsoft link exists.
test-message
- F005: Added
sendTeamsTestMessageImpl()andsendTeamsTestMessage = withAuth(...)inee/packages/microsoft-teams/src/lib/actions/integrations/teamsDiagnosticsActions.ts; it uses the samesystem_settings:updategate assaveTeamsIntegrationSettingsImpl. - 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 ateams_notification_deliveriesrow before returning. - F011: Healthy path builds a proactive bot test activity with text/card title
Alga PSA Teams test messageand sends it throughsendBotActivity()using the storedserviceUrlandconversationId. - F012: Extended
writeTeamsDeliveryRow()to allowdestinationType='bot_test', nullableinternalNotificationId, and anidempotencyNonce; test-message rows usecategory='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.tscovering 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.tspassed 16 tests.
diagnostics
- F013-F014: Added
TeamsDiagnosticsStep,TeamsDiagnosticsReport,TeamsDiagnosticsStatus, andrunTeamsDiagnosticsImpl()with orderedrunStep()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.tswith 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.tspassed 27 tests;npm -w @alga-psa/ee-microsoft-teams run typecheckpassed.
settings-ui
- F025-F032: Added a
Diagnostics & Test Messagecard below the package card inpackages/integrations/src/components/settings/integrations/TeamsIntegrationSettings.tsx. It wiresRun diagnosticsandSend test messageactions, 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 laterwiringgroup. - T034-T039: Added
TeamsIntegrationSettings.diagnostics.test.tsxcovering 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.tsxpassed 6 tests (React act warnings emitted by async state updates);npm -w @alga-psa/integrations run typecheckpassed.
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 everyserver/public/locales/*/msp/integrations.jsonfile. 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.tsto 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, andpackages/integrations/src/actionspluspackages/integrations/src/actions/integrationsre-exportrunTeamsDiagnostics,sendTeamsTestMessage, and their result types forTeamsIntegrationSettings. Added T042 coverage toteamsActions.test.tsso the boundary remains importable. - F038/T043: Rebuilt
@alga-psa/ee-microsoft-teamswithnpm -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. Verifieddist/actions/index.mjscontainsrunTeamsDiagnostics,sendTeamsTestMessage,runTeamsDiagnosticsImpl, andsendTeamsTestMessageImpl. 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.