Services
Dependency graph
| Service | Calls | Why |
|---|---|---|
api-audit | — | Depends on nothing. Everything depends on it. |
api-account | api-audit | To record changes |
api-identity | api-account, api-audit | To read the account behind a login or key; to record mints |
api-libraryset | api-account, api-audit | To put names to the user IDs stamped on records; to record changes |
ui-auth | api-identity, api-account, api-audit, api-libraryset | The login flow |
ui-management | api-account, api-audit, api-libraryset | Administration |
Discovery is Aspire's, declared with WithReference in the AppHost and consumed through the
DiscoverableServices option on AddCSMCommonWeb. No service holds another's URL in configuration.
api-libraryset
The reason the product exists. Tag groups, tags, library sets, variables, values, resolution and import.
Two pieces of logic sit outside the endpoints:
VariableConsumptionHandler— the resolution rule.GetWinningValuesreturns the whole winning value per variable;GetForConsumptionreduces that to a name-to-string map. Both consumption endpoints go through it, so the two cannot disagree about which value wins.LibraryImportPlanner— works out what an import would do before anything is written, so a rejected file is refused before the transaction has changed anything.
It sets IncludeRecordAuthors, which is what wires up the calls to the account API for author names.
api-account
Organizations, users, groups, permissions and API keys.
IdentityProviderVerifier— puts a set of credentials to Entra or Google without storing them. On its ownHttpClientwith a 15-second timeout: checking credentials is something somebody is waiting on, and a provider that has gone quiet should say so rather than hold the page.ApiKey— generating and hashing a key holds no state, so a singleton serves every request.GroupPermissionRules— the grant checks and dependency checks applied when a group is saved.AccountPatternRules— decides whether an email address may have an account created for it.
Enum values are serialised by name (JsonStringEnumConverter) so a payload reads for itself.
api-identity
The only service that signs tokens.
JwtGenerator mints from three starting points — a login, a refresh, an API key — through one private
MintToken, so a caller gets the same session whichever route they came by. A second copy of that would
eventually mint subtly different sessions.
JwkGenerator produces signing key pairs. Operational, run out of band.
Every mint writes a JWT audit entry.
api-audit
The change log. The only service with no dependencies.
AuditVisibilityRulesdecides how much of the log a caller sees — the organizational and global split described in the audit API.AuditLoggingOptions.AuthNotRequiredis the allow-list of entry types that may be written without a JWT. That list is this endpoint's security boundary.
It configures a permissive CORS policy, because the management UI reads it directly from the browser.
ui-auth
Blazor Server. The login page, branded per organization.
| Piece | Does |
|---|---|
OrganizationResolver / CurrentOrganizationMiddleware | Works out which organization a request is signing in to |
OrganizationSchemeProvider | Registers an OIDC scheme per organization per provider, resolved at request time |
ProviderSelector | Offers whichever providers the organization has configured |
EntraPhotoSource | Fetches a profile photo where the provider offers one |
TokenGenerator / TokenCookies | Calls the identity API and writes the x-tagshape-auth cookie |
CustomCssRules | Filters an organization's custom CSS against an allow-list |
Where nothing in front of the deployment sets the organization per request, the x-tagshape-org header names it.
ui-management
Vue 3 with vue-router and Monaco. The only non-C# part of the product, and the one place CLAUDE.md carves out
from the C#-everywhere rule.
Client-side services mirror the server's concepts: permissions.js reads the catalogue from
GET /permissions, token-refresh.js renews the session in the background, current-user.js holds the signed-in
account.
See The management UI.
Health and telemetry
Every service is wired by AddCSMCommonWeb with OpenTelemetry (named per service), Serilog and OpenAPI at
/openapi/v1.json with a JWT auth option. Health check endpoints exist at /healthz/startup; the AppHost's
health-check registrations are currently commented out.