sefe / sefe/dorc

MS Teams bot DM notifications on deployment completion (Bot Framework proactive messaging)

Open
#657 1 comment 0 reactions 2 assignees View on GitHub

@Otar-Khokhashvili-ext-sefe is already working on this.

Since Sep 17, 2026.

  • #669 by @slafeer-sefe — open
area:ui DORC DV 03 enhancement
Dominant language
C#
Stars
5
Forks
3
Avg merge
1d 22h
Merged PRs (30d)
14

Description

Problem

When a deployment request finishes, the only notification surface is the SignalR-driven real-time UI. A user who isn't actively watching the page has no way of knowing their job has finished. We want to push a direct 1:1 MS Teams message to the requesting user when their request reaches a terminal state.

Goals

  • Deliver a Teams direct message (1:1 chat) to the user who submitted a deployment request when that request reaches a terminal state.
  • Message is an Adaptive Card containing: request id, requester, project, environment, build number, final status, start/completed timestamps, duration, and a deep-link back to the DOrc UI (/monitor-result/{id}).
  • Failure-tolerant: notification errors must NOT affect deployment status persistence or other event publishing.
  • Configurable on/off per environment via appsettings.json (TeamsNotification section) and the MSI installer parameters.

Non-goals (this issue)

  • Two-way Teams interaction (approving WaitingConfirmation requests from Teams). Track separately.
  • Email / Slack / generic webhook channels. The IDeploymentNotificationSink abstraction leaves room for them later.
  • Per-user opt-in/opt-out preferences UI. v1 sends to the requester unconditionally.
  • Channel / Team broadcast notifications. v1 is strictly user-targeted DMs.

Delivery model: Bot Framework proactive messaging (no Graph)

⚠️ This section supersedes the original Graph-based design. The Graph chat approach (Chat.Create / ChatMessage.Send + MSAL) ran into problems in our tenant and was abandoned — see comment. No Graph chat permissions are required.

The Monitor sends proactive messages directly through the Bot Framework Connector:

PendingRequestProcessor / DeploymentRequestStateProcessor reach terminal status
  → IDeploymentNotificationSink.NotifyRequestCompletedAsync(request, finalStatus, startedTime, completedTime)
    → TeamsBotNotificationSink: NotifyOnStatuses filter → resolve request.UserName → Entra object id (AzureEntraSearcher)
      → ITeamsConversationClient.CreateConversationAsync (tenant + AAD object id)
      → ITeamsConversationClient.SendCardAsync (Adaptive Card 1.4)

What this design requires:

  1. Azure AD app registration + Azure Bot Service resource with the Microsoft Teams channel enabled — ✅ done (registrations created; currently configured for ut2 only).
  2. The bot app installed for the user in Teams — this is the only per-user prerequisite for receiving proactive DMs.
  3. Bot credentials (BotAppId / BotAppPassword / TenantId) and the regional Connector ServiceUrl (https://smba.trafficmanager.net/uk/) in configuration; secrets sourced from the secret store, never committed.

What this design explicitly does NOT need (all removed from the original plan):

  • ❌ Microsoft Graph chat permissions (Chat.Create, ChatMessage.Send, User.Read.All for chat) / MSAL client-credentials flow
  • TeamsUserConversation conversation-reference table in Dorc.Database
  • ❌ Bot Framework activity endpoint (TeamsBotController) in Dorc.Api
  • ❌ A separate Dorc.Notifications.Teams project — components live under src/Dorc.Monitor/Notifications/

User identity resolution

  • DeploymentRequestApiModel.UserName is populated from the requester's claims (OAuthClaimsPrincipalReader): usually the email claim, falling back to SAM account name; for M2M requests it can be a machine client id.
  • TeamsBotNotificationSink resolves UserName → Entra object id via the existing AzureEntraSearcher.Search() in Dorc.Core (Graph user lookup under the existing AppSettings.Aad* app credentials — distinct from the rejected Graph chat APIs).
  • Resolution failure (or empty UserName) → log a warning and skip the notification; the deployment is never affected.

Implementation status — PR #669 (updated 2026-08-02)

v1 is implemented in #669 (branch feature/teams-notification-bot), including the gap-closure work below:

  • src/Dorc.Monitor/Notifications/IDeploymentNotificationSink, NoOpDeploymentNotificationSink, DeploymentNotificationDispatch (fire-and-forget swallow policy), FatalExceptions
  • src/Dorc.Monitor/Notifications/Teams/TeamsBotNotificationSink (status filter, resolution, Polly retry + cooperative 10s timeout; 4xx/timeout/cancellation not retried), ITeamsConversationClient/TeamsConversationClient (Bot Connector wrapper), DeploymentCompletionCardBuilder, TeamsBotOptions
  • Hookpoints: PendingRequestProcessor terminal sites and DeploymentRequestStateProcessor (cancel / abandon / stale-cleanup / errored-pickup), per-request optimistic transitions so HA monitor pairs never double-DM
  • DI + options in src/Dorc.Monitor/Program.cs (bound via builder.Configuration so env-var/secret overrides work; malformed Enabled disables rather than crashing)
  • Installer wiring: Setup.Dorc (bat, NonProd/Prod wxs, msi.json) for all 7 TEAMS.* parameters
  • Tests: 30+ unit tests (card builder, sink, both processors' notification behaviour) in src/Dorc.Monitor.Tests
  • teams-app/ manifest template + placeholder icons; docs/teams-notifications/teams-notifications-setup.md setup & rollout guide

Configuration (src/Dorc.Monitor/appsettings.json):

"TeamsNotification": {
  "Enabled": "false",
  "BotAppId": "",
  "BotAppPassword": "",
  "TenantId": "",
  "ServiceUrl": "https://smba.trafficmanager.net/uk/",
  "DorcUiBaseUrl": "http://localhost:8888",
  "NotifyOnStatuses": "Completed,Failed,Errored"
}

Remaining work for v1 (tracked on PR #669)

  • Monitor-side Entra config + DI — the AppSettings.Aad* keys were already present in the Monitor's appsettings and installer; the searcher is now injected via DI instead of constructed inside the sink.
  • Tests — unit coverage for card builder, sink (disabled / filter / empty user / resolution failure / retry / swallowed failures) and both processors (fires exactly once per terminal status; faulted sink tasks never affect completion).
  • Code-quality findings from PR #669 — obsolete TrustServiceUrl removed, conversation id null-guarded, generic catches narrowed to non-fatal filters; stray src/src/... duplicate deleted.
  • NotifyOnStatuses filter — configurable, default Completed,Failed,Errored; effective set logged at startup, unknown names warned.
  • Cancel/abandon path coverageDeploymentRequestStateProcessor now notifies on Cancelled/Abandoned/Errored transitions (per-request, duplicate-safe in HA; PendingRequestProcessor defers Cancelled to it).
  • Rollout — manifest template + setup docs are in the repo; still to do operationally: real icons + privacy/terms URLs in the manifest, tenant catalog upload (U3), enable environments beyond ut2.

Deferred (follow-up candidates)

  • Mark TEAMS.BOT.APP.PASSWORD Hidden="yes" in the WiX property definitions so it stays out of verbose MSI logs.
  • Decide notification behaviour for M2M/machine requesters (currently fails safe: skip + log).
  • Card "Duration" uses queue-wait for never-started requests (cancel/abandon paths) — cosmetic.

Acceptance criteria

  • When a deployment request reaches a status listed in NotifyOnStatuses, a Teams 1:1 DM (Adaptive Card with request id, requester, project, environment, build, status, timestamps, duration, deep-link) is sent to the requester within ~10 seconds of status persistence.
  • Notification failure of any kind (credential, network, resolution, missing bot install) does not affect deployment status updates or event publishing; failures are logged.
  • A user without the bot installed simply doesn't receive the DM — logged, deployment unaffected.
  • TeamsNotification.Enabled = false registers the no-op sink with no side effects (unit-tested).
  • Unit + integration tests cover happy path, retry, swallowed-failure, and no-op-when-disabled.
  • No changes to the public deployment API surface; no secret material committed. (Holds in the diff — final check at merge.)
  • (Unchecked items need manual verification in ut2 once PR #669 merges and the bot is installed.)

Unknowns Register

# Unknown Blocking? Status
U1 AAD app + Bot Service provisioning ✅ Resolved — registrations created, configured for ut2
U2 Graph Chat.Create tenant policy ✅ Resolved — Graph chat approach abandoned; Bot Framework proactive messaging requires no Graph chat permissions
U3 Teams app distribution — tenant-wide catalog vs sideload Yes for go-live Open — confirm with platform team
U4 Requester identity mapping ✅ Resolved — UserName is usually the email claim; resolved via AzureEntraSearcher
U5 Behaviour for M2M/machine-account requesters (UserName = client id) No — resolution fails safe (skip + log) Open — decide if machine requests should notify anyone
U6 Adaptive Card schema version ✅ Resolved — 1.4 in use

Original ask: #385 (@benhegartysefe). Graph-based rewrite 2026-04-30; rewritten by @claude on 2026-08-02 to match the as-built Bot Framework implementation in #669 (per @slafeer-sefe's findings); checklist updated 2026-08-02 after the gap-closure work was pushed to #669.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.