IT Helper
Open IT support cases from Teams; the bot emails them to Thrive’s ServiceNow intake and relays status back — no API access required. Live org-wide since 2026-07-15. Renamed from “PSI IT Help Desk” 2026-07-16; internal names (repo paths,
psi-helpdesk--*secrets, functions) keep the originalhelpdesknaming.
What it is
A Teams bot every PSI user has (auto-installed + pinned via app setup policy). Tell it what’s wrong in your own words → it opens a case, emails it to our MSP (Thrive, support@thrivenextgen.com) from the techsupport@progressivesurface.com shared mailbox, and mirrors every case into the internal IT Cases chat. When Thrive’s ServiceNow acknowledges (~3 min), a poller correlates the ack and hands the requester their case number + portal link in the same Teams chat.
It’s a forwarder, not a case tracker. Thrive re-assigns each case to the actual user on intake and owns the relationship (updates, closure) directly from there; the bot never tracks lifecycle, and users never see the internal PSI-nnnn refs (those live in email subjects + the mirror chat for ops correlation only).
Built because Thrive’s platform change retired their Teams ticket bot, right as our primary tech went on paternity leave. Email is an implementation detail the user never sees; the transport swaps for the ServiceNow API without UX changes if Thrive ever grants access.
Code: psi-notify-bot — shares the psi-notify-listener Function App, Bot Framework plumbing, alerts DB, and Key Vault conventions with PSI Notify Bot (separate Teams identity, same backend). Authoritative change history: BUILD_LOG.md in that repo. Plan/architecture/runbook: docs/helpdesk-bot-plan.md.
User experience
| Action | Result |
|---|---|
| Type anything at the bot | New-case form opens pre-filled with what you typed — only the description is required; type/urgency one tap; category/location/on-behalf-of under “More details” |
| 💻 PSI application (ProApps) issue | Expander: which app(s) + “Did you run a ProReset?” — travels to Thrive, who assign PSI’s internal team |
| Submit / Cancel | Submit closes the card (no double-file) + “we’re opening your request” (no case number yet, deliberately); Cancel closes it with nothing filed |
my cases | “Your recent requests” — last 30 days, case numbers linked to the Thrive portal; a receipt lookup, not a tracker |
| Thrive acknowledges | “🎟️ Your case CS… is open with our IT provider” + View at Thrive button |
| Thrive updates / resolves | Forwarded as a card (redundant/duplicate notices are de-duplicated; the [External] banner is stripped from the preview); resolution notices lead ”✅ Thrive marked … resolved” |
| Delivery bounce | Honest “IT is handling it” to the user + a red 🚨 alert in the IT Cases chat |
Department-specific forms
The form tailors itself from the requester’s Entra department at open time (config-driven — no code change to adjust):
- Process Services (dept 125): “⚠️ Is a production line down?” — Yes forces critical urgency, a
[LINE DOWN]subject prefix + banner to Thrive, and a red 🚨 IT Cases mirror card. - HR (dept 180, + named individuals): three launch buttons — ➕ New user, 🔄 Position change, ➖ User termination — each opening a dedicated request card (see below).
Config (Function App settings): HELPDESK_LINEDOWN_DEPTS (default 125), HELPDESK_HR_DEPTS (180), HELPDESK_HR_USERS (specific UPNs that get the HR form regardless of department — for IT to see/troubleshoot: Adam Devereaux, Chris Hosakura).
HR request flows (A/R/C)
HR requests route to Thrive like any case (Thrive assigns PSI’s internal team); each carries a structured payload + an SOP checklist in the email. Full spec + decisions: docs/hr-request-worksheet.md.
- New user (A): starts from the employee number and reads the rest from AFTEC (see below). License/groups derive from a matrix — not picked by HR. Policy: every employee gets a licensed named account.
- Position change (C): new title/dept/manager, effective date, “set up like” model user, remove-old-access (mandatory), equipment/location for the manager.
- Termination (R): three paths, each with its checklist — 1 full delete (shop floor) · 2 preserve & convert (mailbox→shared, manager gets OneDrive, +90-day review) · 3 keep active (atomic password+sessions+MFA reset, credentials to manager, +30-day review). “Immediately” → critical + 🚨. Sender experience silent by default (see
docs/offboarding-ooto-recommendations.md).
Manager is CC’d on all HR emails.
New-user form reads AFTEC (2026-08-06)
The form used to ask HR for seven identity fields. By the time they open it, they have already entered the hire in AFTEC twice over — so it was asking them to retype work they had just finished, in both directions.
HR’s actual sequence: Paylocity ATS → candidate accepts → HR creates the employee in Paylocity (which assigns an employee ID they must verify is non-conflicting) → HR enters them in AFTEC (department, shift, hourly/salary, names, start date, rate) → a second AFTEC screen for direct/indirect, the three-letter user id (AFTEC itself rejects a conflicting one) and the in-house-subcontractor flag → only then the IT request.
So the form now asks for the employee number, reads AFTEC, shows the record back for confirmation,
and asks the one thing AFTEC does not hold: the job title — which is exactly what the licensing
matrix keys on (TITLE is blank on every record checked and sparsely used file-wide).
| Form field | AFTEC source |
|---|---|
| Full name | LAST.NAME + FIRST.NAME (upper-case in AFTEC; the bot title-cases it) |
Initials → sAMAccountName | USERID — uniqueness already enforced by AFTEC |
| Department | DEPT.NO |
| Start date | START.DATE (from the payroll file) |
| Manager | RPT.TO (an employee number) → resolved to a person in Entra, see below |
| Employee / Temp | SUBCONTRACT = Y is the in-house sub; EMPTYPE D/I is direct/indirect labour |
| Shift | SHIFT (D/N) — shown; door groups differ by shift |
| Job title | not in AFTEC — asked |
Two record-key formats, neither guessable (verified live 2026-08-06):
EMPLOYEE.PUBLIC.1287—CO!EMPNOwith the number zero-padded to four:1!0793,1!4200.1!793is a 404.EMPLOYEE(payroll) —COconcatenated with that padded number, no delimiter:10793,14200. Follows the dictionary’s ownTRANS(EMPLOYEE,CO:EMPNO,…)expression. This is where the start date lives, already converted toyyyy-MM-ddby the API.
The manager is resolved, not asked for. RPT.TO is an employee number, and Entra stamps that
same number on the user — unpadded (793, not 0793, the opposite of AFTEC’s record keys). So the
card filters the directory on employeeId, shows the manager by name
(Dennis Pearo (DPearo@progressivesurface.com)), and offers the same “More — someone else is the
manager” override the flat departments use instead of a question. Three deliberate exceptions:
- a disabled account keeps the question and is flagged — AFTEC reporting lines point at leavers, and provisioning a hire under an unreachable manager is worse than one more field;
- an unresolvable number says so rather than displaying a bare number;
- AFTEC’s own derived
EMAILfield (FIRST.NAME[1,1]:LAST.NAME:"@PROGRESSIVESURFACE.COM") is not used as a fallback. It matches PSI’s convention and would be right most of the time, which is not good enough for who someone reports to.
This removes most of the reason to hand-fill the supervisors lists in hr-org.json (all still empty
placeholders) for AFTEC-sourced hires: a per-person answer from the ERP beats a hand-maintained list.
Identity model: the read is delegated. The listener’s system-assigned managed identity holds the
UniData.ActAsUser app role on PSI.UniData.API, and each call carries
X-On-Behalf-Of: <the HR person's UPN>; the API opens the UniData session as that person using their
own credentials, so AFTEC’s audit shows Gina or Karen, not a service account. The header without the
role is a deliberate 403, never a silent downgrade. The bot therefore holds no UniData credential
of its own.
Fail-soft by design. No AFTEC record, an inactive one, a department AFTEC names that the HR list does not have, or the API being unreachable all land back on the lookup card with a plain sentence and “Enter it manually instead” one tap away — which re-opens the original department-first form. A person starting Monday must never be blocked because the ERP is slow.
Why the first deploy of this hung (fixed 2026-08-07). The lookup timed out after 101 seconds.
api.progressivesurface.com is PS-PROXY at 10.150.141.9, and Azure reaches on-prem only through the
Meraki vMX via the MerakiRouteTableAZ-GR route table — which was not attached to ps-flexfunc, the
subnet this Flex Consumption app egresses through. DNS had been correct the whole time, which is exactly
what made it look like a DNS fault. See
Deploy to Azure §5.
Two things changed as a result: the subnet got the route table, and the lookup got an 8-second budget
(both employee files read in parallel) with a card that distinguishes “no AFTEC employee 4200” from
“couldn’t reach AFTEC just now” — a 100-second hang is worse than a fast honest answer, and Teams
abandons a card action at ~15 s anyway. GET /api/alerts/status now carries a unidata block
(reached, HTTP status, elapsed ms) so on-prem reachability is checkable without asking HR to tap
anything.
How cases reach Thrive (two transports)
Thrive told PSI on 2026-07-28 that cases opened by email are triaged at a low priority and asked us to submit through their client portal instead. A proper ServiceNow API integration is in progress on Thrive’s side but stuck in their approval process, so the portal is the near-term path.
| Email (original) | Portal (new) | |
|---|---|---|
| How Thrive sees it | contact_type = Email → low priority | contact_type = Client Portal → normal triage |
| Case contact | techsupport@, re-assigned to the real user by Thrive’s SOP | the actual requester, from creation |
| Case number | minutes later, scraped from the ack email | returned synchronously |
portal.thrivenextgen.com is Thrive’s own Next.js app (not ServiceNow’s UI), SSO-federated to PSI’s Entra tenant, with a backend that thinly proxies ServiceNow’s Table API — POST /api/table?endpoint=cases to create, GET /api/table?table=…&query=… to read, GET /api/scriptedrest?endpoint=downstream-subcats for the cascading category picklists. It has no service-to-service auth, so a scheduled Playwright job on PS-PROXY signs techsupport@ in over Entra SSO every 6 hours and stores the session cookie as psi-helpdesk--thrive-portal-cookie; PS-PROXY specifically, because the service account’s Conditional Access exemption is scoped to that host’s egress IP.
HELPDESK_TRANSPORT (portal | email, default email) picks the primary. Any portal failure falls back to email and records a case event — a portal outage can never cost a user their request. Rollback is one app setting.
Status: code merged 2026-07-28 behind the flag, not yet enabled. Pending: the create-case request envelope (captured from the next real submission via scripts/thrive-session/capture-payload.js --submit), the techsupport@ portal credentials in Key Vault, and the CA exemption. Full detail: docs/thrive-portal-transport.md in the repo.
Architecture
User (Teams) → /api/helpdesk/messages (psi-notify-listener, Flex Consumption)
→ INSERT dbo.Cases → Graph sendMail (techsupport@ → support@thrivenextgen.com)
→ confirmation card → mirror card to IT Cases chat (activity id stored)
case-mail-poller (2-min timer) → reads techsupport@ inbox (read-only; watermark
+ claim→processed, at-least-once) → correlates [PSI-nnnn] / CS number
→ ack: case number + portal link to requester, updates mirror card in place
→ later mail: dedupe + strip [External] banner → forward; bounces → 🚨 IT Cases
- Caller identity: all mail reaches Thrive from
techsupport@, so the email carries max identity — requester name in the subject, body block with name/email/title/dept/office/phone (GraphUser.Read.All),Reply-To= requester +techsupport@. - Mailbox never mutated (
Mail.Send/Mail.Readscoped totechsupport@only via an Exchange application access policy); dedupe lives indbo.MailboxWatermark+dbo.CaseInboundMail. - Forwarded-update hygiene: Thrive sends redundant notices (observed: two identical “Resolved” 28s apart + a nudge 2 days later); the poller suppresses repeats (dedupe on subject+body, coalesce resolution notices) and strips the
[External]CAUTION banner from previews. - Data:
dbo.Cases/dbo.CaseEvents/dbo.CaseInboundMailin thealertsDB (procserv-proddata). Schema applies viaPOST /api/helpdesk/migrate(function key; listener MI has ddladmin) — re-run after any deploy that adds columns.
Azure resources
| Resource | Name | Notes |
|---|---|---|
| App registration | IT Helper | App ID a2fb6b1e-3cdf-4325-8db5-4f850151f76a; Graph Mail.Send, Mail.Read, User.Read.All (application, admin-consented) |
| Azure Bot | psi-helpdesk-bot | PS-WEBAPPS, F0, Teams channel |
| Teams app | IT Helper | Org catalog ID b8369c60-9e93-4c63-9179-f2a645988cd2; auto-install + pin via app setup policy; green “IT” icon |
| Function App | psi-notify-listener (shared) | Routes api/helpdesk/*; poller gated by HELPDESK_POLL_ENABLED=1 |
| App settings | — | HELPDESK_POLL_ENABLED, HELPDESK_LINEDOWN_DEPTS, HELPDESK_HR_DEPTS, HELPDESK_HR_USERS |
| Secrets | ps-certificates-kv | psi-helpdesk--* (tenant/client id+secret, mailbox, msp-intake-email, msp-portal-case-url, chat-it-cases, teams-app-catalog-id) |
| Provisioning app | psi-hr-provisioning | Enterprise app, SP ded5f229-2296-4db5-9cd5-d16d8f3c201b; API-driven inbound provisioning to on-prem AD (ad.ptihome.com) |
| Provisioning identity | id-psi-provisioning | User-assigned MI 7a1571e8-5ae4-4576-86b2-76be744ed273 on psi-notify-listener; Graph SynchronizationData-User.Upload, ProvisioningLog.Read.All, Organization.Read.All |
| Group-add account | userprovisioning | AD account with WriteProperty on member across OU=Groups,OU=PTI — provisioning sets no group membership |
| Mirror chat | “IT Cases” | 19:cf22f43c5e2147eaa0c8b434c3b50a76@thread.v2 — Dakota Cooper, Adam Devereaux, Chris Hosakura, Power Operative |
Operations
- Intake address: the
psi-helpdesk--msp-intake-emailsecret (process-cached — restart/redeploy the listener to apply deterministically). - Form flavors: the
HELPDESK_*_DEPTS/HELPDESK_HR_USERSapp settings (comma-separated; dept = name-prefix match, users = UPN/email). Change without a deploy. - Schema:
POST /api/helpdesk/migrate(function key) appliessql/helpdesk-schema.sqlvia the listener MI — run after any deploy that addsdbo.Casescolumns. - POC / testing without Thrive: point the intake secret at an internal mailbox;
scripts/Send-FakeSnowAck.ps1simulates ServiceNow Ack/Update/Resolved. - Provision from scratch:
scripts/Setup-HelpdeskBot.ps1+ the runbook indocs/helpdesk-bot-plan.md. - Telemetry: App Insights
psi-notify-listener; poller logsCase PSI-nnnn acknowledged as CS…,Forwarded provider …,Suppressed a redundant provider forward …,Provider delivery FAILED …. - “Failed to send” + IT Helper missing from Teams search → check for store lookalikes (incident 2026-08-04). Public Teams-store apps named “It Help Desk Bot” (one with a Spanish description) outranked/shadowed the org app in client search; users messaged the lookalike and its dead endpoint returned “failed to send”, while the real bot’s telemetry showed nothing arriving. Diagnosis signature: zero
helpdesk-messagesrequests in App Insights while the endpoint answers a direct probe, and other users (still pinned to the real app) unaffected. Fix: Teams admin center → Manage apps → block the lookalikes’ availability. Prevention worth considering: restrict third-party store apps by default in the org app permission policy.
Automated user provisioning (live)
New-user requests are moving from “Thrive provisions by hand” to Entra API-driven inbound
provisioning: the listener POSTs a SCIM record to /bulkUpload, the Microsoft provisioning
agent writes the user into on-prem AD, and Entra Connect syncs it back to the cloud.
Status (2026-08-13): live end to end. HELPDESK_PROVISIONING_ENABLED=1,
PROVISIONING_POST_TASKS_ENABLED=1, PROVISIONING_MATRIX_SQL_ENABLED=1, and
/api/alerts/status reports origin: "Sql". The Thrive path remains the fallback for a case the
matrix cannot resolve or a row nobody has signed off.
Accounts are created ENABLED. The plan’s “no dormant enabled account” rule deferred the enable to a start-date job that was never built, so every hire landed disabled and stayed that way until a human noticed — Kurt Leatherman included. The rule was removed, not the accounts (#70, 2026-08-11). Anything that still says “disabled until the start date” is out of date.
Existing accounts are named on the card (2026-09-09, psi-notify-bot #105). The upload matches on
employeeID, so a “new user” whose employee number already has an account becomes an Update of
that account. PSI-1038 found this out as a failed rename (Sam → Samuel, INSUFF_ACCESS_RIGHTS) four
hours after approval. At submit the listener now looks the employee number up in the directory and, when
a person comes back, the approval card carries an ⚠️ notice: the account, enabled or disabled, its login,
that ✅ will update it (a rehire or a conversion), and two warnings when they apply — HR’s name differs
from the account’s (the job cannot rename; fix AD first or ✋), and the form’s initials are not the
account’s login. dbo.CaseEvents records existing_account_detected. The tap is not blocked; what to do
about a rehire is #94.
Seat check on signed rows (2026-09-09, #104). Signed policy rows name the Entra licensing group
(LIC - M365 Business Premium); the SKU map now strips that prefix, so the seat guard runs for signed
rows too. Before this it silently skipped them (seat_check_unknown) and only protected the drafts.
How it works
HR completes the New user card → approval card in IT Cases → ScimPayloadBuilder emits a SCIM
BulkRequest → ProvisioningDispatcher POSTs it → the provisioning job applies attribute mappings
→ the agent creates the AD object → ProvisioningLogPollerFunction correlates the result back to
the case.
It is request-driven, not a directory sync. Only users explicitly sent are touched; a user absent from a payload is never modified or deprovisioned. There is no dry-run mode — a POST creates a real AD object.
What the listener must send
| SCIM attribute | Carries | Becomes in AD |
|---|---|---|
externalId | Employee ID | employeeID — the matching attribute |
userName | Full UPN, FLast@progressivesurface.com | userPrincipalName, mail |
nickName | 3-letter initials | sAMAccountName |
displayName | Full name | displayName, CN — an Update that changes this is a rename, which the agent was refused on PSI-1038; confirm the mapping is Create-only in the Entra job |
title | Job title | title and description |
userType | Employee / Temp / Contractor | employeeType |
department | Canonical NNN - Name | department, and the OU |
enterprise manager.value | Manager’s employee ID | manager (resolved to a DN) |
active | true on create | the account is created enabled |
22 attribute mappings in total. OU placement is derived from the leading 3-digit department
code, so a department being renamed cannot break it, and users land in
OU=<NNN - Name>,OU=Users,OU=PTI. There is no separate OU for temporary staff — temp status is
recorded in employeeType.
Safety posture
| Guard | Setting |
|---|---|
| Target object actions | Add,Update — Delete removed |
| Accidental deletion threshold | 5 |
| Skip out-of-scope deletions | Enabled |
| AD Recycle Bin | Enabled, 180-day retention |
| Notification recipient | Set (an empty value quarantines the job on start) |
userPrincipalName, mail, sAMAccountName, CN and displayName are create-only — they are
written once and never overwritten by a later change request. Everything else follows HR.
Where the policy lives — PSI Portal (live 2026-08-06)
Which license and which groups a 125 / Process Technician gets started life as
listener/Data/licensing-matrix.json, compiled into the Function App — so changing one group meant
edit, commit, redeploy, and no row had ever been signed off. That policy now lives in
PSI Portal, SQL-backed in the same alerts database: the listener reads a view
(dbo.vw_prov_matrix_json) on a 5-minute TTL and keeps the embedded file only as the fallback of
record. Design and build order: docs/User Provisioning - on prem helper plan.md in the repo.
Live since 2026-08-06 — PROVISIONING_MATRIX_SQL_ENABLED=1, the schema is applied in alerts and
seeded (18 departments, 8 rules, 425 group rows, 79 title refs), and /api/alerts/status reports
origin: "Sql". HELPDESK_PROVISIONING_ENABLED=1. The rules that govern it:
- A provisioning decision never comes from the fallback.
PROVISIONING_MATRIX_REQUIRE_SQL(default on) turns “serving the embedded copy” into an ordinary Thrive case — a department IT deleted in SQL still exists in the built-in file. - Unsigned = draft. An unconfirmed department row blocks the ✅ tap. The approval card still
posts (that is where IT learns the row needs confirming) with the reason, a link to the row, and
✋ Send to Thrive live.
PROVISIONING_ALLOW_UNCONFIRMED=1pushes a single pilot hire through. - No seat, no hire — and it resumes itself. Before uploading, the dispatcher checks free seats for the
derived SKU. A dry pool parks the case as
waiting_licenseand files a seat-purchase case with Thrive (Thrive sells the seat; it no longer creates the account). Since 2026-08-10 a 15-minute sweep re-checks parked hires and resumes the approval automatically once a seat exists, keeping the original approver’s name in the trail — before that the status was written and never read, so a hire sat parked forever and the card offered no way to retry. An unmapped or PROPOSED licence has no seat gate at all, deliberately: a confident number about the wrong pool is worse than none. - The approved decision is snapshotted to
dbo.Cases.ProvisioningPlanJsonat the tap, because by the time Entra reports the account created the matrix is minutes older and editable — the hire gets what IT approved, not what the matrix says later. GET /api/alerts/statusreports which matrix is in force (version,origin,departments,loadedUtc).origin: "Sql"is the only trustworthy sign that portal policy is live — no doc can tell you.
Known gaps
Group membership is not provisioned yet.Closed 2026-08-10. Onprovisionedthe listener enqueuesset_manager+ oneadd_ad_groupper on-prem group intodbo.prov_post_tasks(psi-portal’s table), and theuserprovisioningworker on PS-PROXY claims them. Proven on case 1029:set_managerand eight groups — including all three door groups — every one done on the first attempt, in seven seconds.PROVISIONING_POST_TASKS_ENABLEDis on. Entra dynamic groups (DG - <dept>,All email users,Progressive Surface) still attach themselves once the account is enabled. A 15-minute sweep cards failed tasks into IT Cases so a half-provisioned hire cannot stay invisible.managercannot be set by the provisioning job. Entra only resolves reference attributes for objects the job itself processed, so the mapping silently skips anyone else — which is whyset_managerbelongs to the on-prem worker, and why the Entra mapping comes out once the worker proves itself (psi-notify-bot#42).- Legal vs preferred name. HR does not yet supply them separately, so
displayNameis pinned to create-only to avoid overwriting someone’s preferred name. employeeHireDateis not an on-premises AD attribute; feeding Lifecycle Workflows needs anextensionAttributeplus an Entra Connect rule.- AFTEC records rehires 0.2% of the time, so employment status derived from its termination date is unreliable — relevant when the input source moves from the HR form to a system feed.
Related docs (in the repo)
| Doc | Purpose |
|---|---|
docs/helpdesk-bot-plan.md | Design, forwarder model, ops runbook, MSP asks, phases |
docs/hr-request-worksheet.md | HR A/R/C field/policy decisions (answered) |
docs/licensing-matrix.md | Original dept→license/groups matrix — superseded by the SQL policy in PSI Portal |
docs/User Provisioning - on prem helper plan.md | Provisioning design + build order (GHE issue #42 is the tracking issue) |
docs/offboarding-ooto-recommendations.md | Departed-mailbox sender-notification policy (for org discussion) |
BUILD_LOG.md | Authoritative dated change history |
Open items (2026-08-13)
Tracked in GHE rather than here, so this list stays short and the issues stay authoritative:
- [#93] Terminations should run through the provisioning machinery. A hire is provisioned by us; a
termination is still a checklist Thrive works by hand.
disable_ad_useralready exists in the PS-PROXY worker and nothing enqueues it. The dangerous half of the lifecycle is the manual one. - [#94] Rehire / co-op conversion is a fourth HR path that works today only by accident: the SCIM
matching attribute is
employeeID, so a New user request with an existing number matches and UPDATES rather than creating. If Paylocity issued a NEW number for the conversion, you get a second account. - [#95] Provisioning only starts one way — HR, in Teams, from an employee number. A name lookup should search Entra (which also reveals an existing account, half of #94), and PSI Portal should be able to start a request rather than only watch one.
- Portal transport go-live.
HELPDESK_TRANSPORTis unset, so every case still reaches Thrive by email and is triaged at low priority — Thrive told us so on 2026-07-28. Needs a captured create-case payload, thetechsupport@portal credentials, and the CA exemption. Highest-value item on this list: it changes how quickly every PSI request gets looked at. - AFTEC is coarser than our matrix in at least one place. AFTEC has 110 and no 111, so a controls
engineer reads as Mechanical. The intake now makes the department correctable with the reason attached
(2026-08-13), but nobody has compared AFTEC’s full
DEPT.NOlist against the 18 in the matrix — other departments split after the ERP was populated would have the same silent failure. - Most policy rows are unsigned and, until each department’s
licensenames a realLIC - *group, a hire provisions unlicensed. Both are now fixable in PSI Portal’s access matrix. - Sign-off and policy editing need
PROVISIONING_EDITOR_UPNSset on thepsi-portalApp Service. The gate is fail-closed, so an empty value means nobody can edit or sign off — and it looks exactly like broken code. Set 2026-08-13 for ADevereaux + CHosakura. instancein/api/alerts/statusreports “local” on Flex Consumption — cosmetic, but it makes per-instance matrix state unverifiable from outside.- Reply-to-Thrive from Teams (Phase 3): provider updates that ask the user a question can currently only be displayed.
Confirmed working since the last revision of this page: the AFTEC lookup end to end
(unidata.reached: true, 55 ms — the Meraki route-table fix holds), group membership via the PS-PROXY
worker, the initial-password handover, and set_manager.
- Remove the Entra
managermapping —set_manageris now proven live through the PS-PROXY worker (case 1029, 2026-08-10), which was the only precondition (psi-notify-bot#42, build-order step 14). - Redeploy the worker for
set_password— PSI Portal can now generate a hire’s initial password and queue the on-prem set, but the service on PS-PROXY predates that task type, so the queue row would sit unclaimed untilInstall-PostTaskWorker.ps1 -Applyruns. See PSI Portal → Initial-password handover. instancein/api/alerts/statusreports “local” on Flex Consumption — cosmetic, but it makes per-instance matrix state unverifiable from outside.- Portal transport go-live — capture the create-case payload on the next real new-user request (
scripts/thrive-session/capture-payload.js --submit), addpsi-helpdesk--thrive-portal-user/--thrive-portal-password, CA exemption for PS-PROXY’s egress IP, install the session refresher, then setHELPDESK_TRANSPORT=portal. - CS4653129 cancelled itself — the 2026-07-28 portal test case flipped to
Cancelled~77 s after creation, cause unknown. Confirm a normally-worded case survives before trusting create-and-forget. - Sign off the remaining policy rows — the matrix is now editable in PSI Portal and seeded, but a rule IT has not confirmed still blocks automatic provisioning by design. Department 125’s shift-supervisor rule (8 on-prem groups) is signed; the rest are drafts.
- Auto-file the 30/90-day termination reviews — the checklist states them; auto-creating the review case at the deadline needs ScheduleFunction integration.
- Reply-to-Thrive from Teams (Phase 3) — provider updates that ask the user a question (“do you need us to do anything?”) can currently only be displayed; replying on the thread would reach Thrive.
- OOTO org decision — confirm “silent” stays the default for departed mailboxes, or move customer-facing roles to auto-reply (
offboarding-ooto-recommendations.md). - Phase 3 backlog: message action (“Create IT case” from any Teams message), screenshot/attachment ingestion, interactive manager card for position-change equipment, AlertEngine voice hook for “I can’t work” / line-down.
Done since launch: rename + green icon, org-wide setup policy, Thrive techsupport@ contact registration (their ServiceNow drops unknown senders), forwarder-model rework, mirror-card ack update, dept flavors, ProApps fields, HR A/R/C flows, forwarded-update dedupe/preview cleanup.