Skip to main content

Services

Dependency graph

ServiceCallsWhy
api-auditDepends on nothing. Everything depends on it.
api-accountapi-auditTo record changes
api-identityapi-account, api-auditTo read the account behind a login or key; to record mints
api-librarysetapi-account, api-auditTo put names to the user IDs stamped on records; to record changes
ui-authapi-identity, api-account, api-audit, api-librarysetThe login flow
ui-managementapi-account, api-audit, api-librarysetAdministration

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. GetWinningValues returns the whole winning value per variable; GetForConsumption reduces 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 own HttpClient with 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.

  • AuditVisibilityRules decides how much of the log a caller sees — the organizational and global split described in the audit API.
  • AuditLoggingOptions.AuthNotRequired is 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.

PieceDoes
OrganizationResolver / CurrentOrganizationMiddlewareWorks out which organization a request is signing in to
OrganizationSchemeProviderRegisters an OIDC scheme per organization per provider, resolved at request time
ProviderSelectorOffers whichever providers the organization has configured
EntraPhotoSourceFetches a profile photo where the provider offers one
TokenGenerator / TokenCookiesCalls the identity API and writes the x-tagshape-auth cookie
CustomCssRulesFilters 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.