Skip to main content

Services

Dependency graph​

ServiceCallsWhy
api-audit—Depends 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.

It also sets IncludeCreditMetering. GET /variables/{id}, GET /variablesets/{id}, GET /variablevalues and GET /variablevalues/preview carry .RequireCredits(...): before one runs, the service reads the balance from the account API's GET /organizations/me/credits (refusing with 429 if it cannot be afforded), and after a 2xx it publishes a $1 GET /export is metered by ExportCreditMeter instead, because its cost is one credit per library set exported. See Credits.

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.
  • Credits — serves GET /organizations/me/credits (used and total credits for the caller's organization) and consumes the credits-spent queue, adding each message's Amount to the organization's used credits. A subscription payment (PAYMENT.SALE.COMPLETED) resets used credits to 0. See Credits.

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.