In progress. This work is being done now. It is not finished, and is not presented as finished.
TenCtrl
TenCtrl signs a technician in with Microsoft Entra ID, searches Microsoft 365 users and opens a read-only User Support 360 identity view from live Microsoft Graph data. Diagnostics, actions and verification are not built yet.
Overview
TenCtrl (Tenant Control) is a support workstation for helpdesk and IT support teams. It gathers the Microsoft 365 evidence a technician needs to diagnose, resolve, verify and document a user incident, so the work starts from the user instead of from one admin portal per service.
Problem
Microsoft 365 support work is split across Entra, the Microsoft 365 admin center, Intune, Service Health and the ticketing tool. The portals are organized by service while technicians start from a person's problem. Technicians also need dated, sourced facts rather than guessed root causes, and actions such as session revocation are only finished once the result is observed.
Constraints
- Evidence first: every fact shown carries its source and retrieval time.
- Microsoft remains the source of truth; TenCtrl does not replace the admin portals and keeps no full copy of Microsoft objects.
- No AI diagnostics in V1: APIs, deterministic rules and evidence only.
- Least privilege: each Microsoft Graph permission is requested only when a shipped capability needs it.
- Unknown is not failed, submitted is not succeeded, succeeded is not verified.
Approach
Build in short, verified sprints. Each sprint ships one capability end to end against a real Microsoft tenant, with typed Graph mappers, explicit failure states and tests, then gets a visual check on a private development host before the next sprint starts. Work is split between a human product owner, Claude Code for orchestration and review, and Codex for implementation.
Key decisions
Next.js modular monolith with server-side Microsoft Graph access.
One repository and one runtime serve the interface and the server routes for sign-in and Graph calls. Same-origin routes avoid a separate API host for V1.
Alternatives considered
- Separate frontend and API services.
- Call Microsoft Graph from the browser.
Keep Microsoft tokens on the server.
MSAL Node runs the authorization code flow as a confidential client. The browser only holds an opaque, HTTP-only TenCtrl session cookie; tokens never reach React components, logs or API responses.
Alternatives considered
- Single-page app with tokens in browser storage.
Derive the tenant context from validated token claims.
The tenant a technician works in comes from the Microsoft session, never from a browser parameter, so a crafted request cannot switch tenants.
Alternatives considered
- Pass the tenant identifier in the URL or request body.
Drizzle ORM instead of Prisma for the foundation.
During setup the Prisma CLI dependency tree reported high-severity audit findings; Drizzle kept the starter dependency tree small and clean.
Alternatives considered
- Prisma ORM.
Label live identity data as AVAILABLE, not HEALTHY.
Reading a profile proves the data is retrievable, not that the account is healthy. Health verdicts wait for deterministic diagnostic rules; capabilities not built yet stay NOT TESTED.
Alternatives considered
- Show green health states as soon as data loads.
Result so far
- Sprints delivered
- 4 (Sprint 0 to Sprint 3)
- Delegated Graph permissions
- 3, read-only
- Unit tests at Sprint 3
- 54
A technician can sign in with a Microsoft work account, search users by name, email or UPN, and open an identity view showing account state, job title, department and manager, each with its Microsoft Graph source and retrieval timestamp. All of it was validated against a live tenant on the private development host.
Learnings
- A visible development host from Sprint 0 makes every sprint checkable in a browser, not only in a test report.
- Requesting every scope at sign-in means one missing admin consent can block features that do not need it; scopes should follow capabilities.
- A crash that looked like a session regression was a date-formatting option unsupported by the server runtime: read the actual error digest before changing auth code.
- Access logs are part of the security surface: OAuth callback query strings had to be removed from nginx logs.
What exists today
| Capability | State | Microsoft Graph |
|---|---|---|
| Sign in with Microsoft Entra ID | Live-validated | User.Read |
| Tenant connection and organization | Live-validated | GET /me, GET /organization |
| User search by name, email or UPN | Live-validated | User.ReadBasic.All, GET /users with $select, $filter, $top=20 |
| User Support 360 identity view | Live-validated | User.Read.All, user profile and manager |
| Authentication methods, sign-ins, licenses, devices, Service Health | Not built | — |
| Diagnostics, actions and verification | Not built | — |
Evidence boundary
The development host is private. This page does not claim customers, production use, or any capability beyond the table above. The public product page is tenctrl.fr.
How a sprint is closed
A sprint is done when the code, typecheck, tests and build pass and the
feature has been checked in a browser against a real Microsoft tenant. Graph
calls go through explicit $select lists and typed mappers; raw Microsoft
objects never reach the interface. Permission, throttling, not-found and
Microsoft-unavailable cases each have their own visible state.
Delivery Method: From Foundation To Live Evidence
The work was deliberately sequenced so a support screen would not outrun its security or evidence model.
- Foundation first (2 October). The repository, tenant-scoped schema, evidence vocabulary, application shell and private development host were created before live data was connected. Mock content remained labelled as mock, which kept the interface useful without implying that it represented a customer tenant.
- Connection second. Microsoft sign-in was implemented as a confidential server-side flow. The browser receives an opaque HTTP-only TenCtrl session, while Microsoft tokens remain in the server-side session store. Tenant context comes from validated claims rather than from a route or form value.
- One bounded read path. User search was added with a small, delegated, read-only permission set. Queries are normalized, Graph fields are selected explicitly and known failure modes such as missing consent, insufficient permissions and throttling are presented as states rather than hidden by a generic error.
- Evidence before interpretation. The User Support 360 identity view was added only after the search path could retrieve live records. It shows identity facts with source and freshness; it does not convert an available Graph response into a health verdict.
- Close the loop before the next capability. Each increment was checked by typecheck, tests and build, then exercised in a browser against a real Microsoft tenant. Capabilities that have not completed this sequence remain labelled as not built.
This sequence is why the current scope is intentionally narrow: live read-only identity data is available, while diagnostics, write actions and verification workflows remain future work rather than implied features.