Skip to content
bitzorcas
中EN

Guide

Operations Governance, Adapters, Configuration, and Tenants

Deep guide to the governance graph, tenant paging, adapter classification, Runtime License states, config-spec aggregation, and their false-positive boundaries.

Last updated

These queries can power an operations overview, but they return diagnostic projections, not a final health verdict. A useful console must distinguish registered, default, reachable, correctly configured, and required-by-profile.

1. Four query paths

governance

AppModuleRegistry + BoundaryVerifier

tenants

ITenantStore.ListAsync

adapters

IServiceProvider.GetService

config

ConfigDiagnosticSpecSource

[ConfigKey] scan

Host extra specs

All four are read-only. Governance and adapter results are process snapshots. Tenants depend on a persistent store. Config reads the already-built IConfiguration.

2. How governance is built

GetGovernanceReportAsync creates an AppModuleBoundaryVerifier and AppModuleDependencyGraph against AppModuleRegistry, producing:

  • total registered modules;
  • direct dependencies per module;
  • missing-dependency messages;
  • whether cyclic modules exist;
  • Mermaid dependency graph text.

It stores no history, diff, approval, or ownership. A release pipeline must retain and compare manifests to answer what changed since the previous deployment.

3. Governance release example

Turn a snapshot into a release decision
var report = (await operations.GetGovernanceReportAsync(cancellationToken))
.GetValueOrThrow();
// ① Missing dependencies and cycles both block release.
if (report.HasCircularDependencies || report.MissingDependencies.Count > 0)
return Result.Failure(ReleaseErrors.InvalidModuleGraph);
// ② Persist structured nodes; Mermaid is not the sole evidence format.
await evidence.SaveAsync(report.Modules, cancellationToken);
return Result.Success();

4. Tenant paging is not an export

GetTenants.Query defaults to PageIndex=0 and PageSize=20. A negative index becomes zero. A size at or below zero or above 100 becomes 20; it is not clamped to 100.

The handler forwards these values to ITenantStore.ListAsync. The response has no total, continuation token, or snapshot version. A client cannot use it as a guaranteed gap-free full export.

5. Tenant examples

Page tenant summaries
GET /api/operations/tenants?pageIndex=0&pageSize=50
Authorization: Bearer <token-with-operations.tenants.view>
Server-side paging policy
// ① Normalize negative pages.
var pageIndex = request.PageIndex < 0 ? 0 : request.PageIndex;
// ② Invalid or oversized values fall back to 20, not 100.
var pageSize = request.PageSize is <= 0 or > 100 ? 20 : request.PageSize;
return await operations.GetTenantsAsync(pageIndex, pageSize, cancellationToken);

6. What adapter status probes

OperationsService lists roughly 35 Application-visible ports across files, events, features, tenancy, Identity stores, audit, notifications, documents, webhooks, billing, website, tickets, and chat.

It calls IServiceProvider.GetService(portType). The answer is what object DI resolves, not whether the object’s database, broker, or vendor endpoint is reachable.

Infrastructure-private ports are absent, so this is not a complete dependency inventory.

7. External connector readiness is a separate report

GET /api/operations/connectors returns a connector readiness report with a different dimension from the adapter report. It covers 8 business connectors (Legal: ElectronicSignature, EnterpriseInfo, LegalDatabase, Express, SfLegalDocument; Tools: IManage, Ocr, Crawler) and for each reports Provider, Status (Configured/NotConfigured/Unavailable), AdapterRegistered, AdapterImplementation, ConfigurationSection, ChangesRequireRestart, and DependentCapabilities.

The two reports differ in what they answer: the adapter report answers “can the container resolve this internal port,” classified by implementation-class-name prefix; connector readiness answers “does this external system’s config section exist and is its adapter registered,” surfacing a fail-closed Unavailable proxy when a provider SDK is missing. A console should keep them separate: one reflects composition topology, the other reflects external-integration boundaries. Neither issues a real connectivity probe, and ChangesRequireRestart is always true, meaning config changes need a process restart.

8. Classification rules and false positives

Regular ports are classified mostly by implementation type-name prefix:

PrefixStatus
Unavailable* or unavailable proxyUnavailable
InMemory*, Memory*, Null*Default
Cap*, Redis*, Cidr*, SignalR*Production
Local*Production (Local)
anything elseUnknown

This is a naming-convention check. A disconnected Redis adapter still appears Production; a mature adapter with a different name can appear Unknown.

9. Runtime License is different

IRuntimeLicenseProvider reports its actual six-state status instead of a type-name category. An absent provider is explicitly Unavailable.

The UI should show license state, reason, and freshness separately instead of reducing it to the same green badge used for naming classification.

10. Where config specifications come from

ConfigDiagnosticRegistry.Create scans loaded assemblies whose names begin with BitzOrcas:

  • class-level and public instance-property [ConfigKey] attributes;
  • Host declarations for connections, Redis, RabbitMQ, OTel, persistence, audit, payment, storage, webhooks, and runtime licensing;
  • first declaration wins for duplicate keys.

An assembly throwing ReflectionTypeLoadException is skipped as a whole without adding a visible scan-error entry.

11. Config diagnostics only inspect value shape

CheckConfig reads IConfiguration[key] and emits:

  • missing for blank values;
  • too-short below MinLength;
  • configured otherwise;
  • only secret length, never the value.

It does not parse URIs, Cron expressions, CIDRs, or provider enums, open a connection, or validate cross-key consistency.

12. Fallback when no spec source exists

If IConfigDiagnosticSpecSource is absent or empty, only Jwt:Secret, Jwt:Issuer, and Jwt:Audience are checked.

A green unit-test or Shell report may therefore cover only three keys. The console should show declaration count and source so operators do not mistake fallback for a full production inspection.

13. Better console representation

Separate declaration, composition, active health, and profile requirement:

Suggested adapter state model
IWebhookDeadLetterQueue
composition: Production # type-name classification
readiness: Healthy # active probe
configuration: Complete # configuration diagnostics
requiredByProfile: true # selected product profile

Current Operations provides only part of the first two dimensions. Runtime health, profile manifests, and release evidence must complete the view.

14. Security and privacy

Implementation names disclose topology. Configuration keys disclose enabled vendors and components. Exact view permissions are required, but responses also need cache, audit, and sensitive-output review.

The tenant list is platform-wide data rather than current-tenant data. Grant operations.tenants.view only through platform administration governance.

15. Existing evidence and gaps

Current tests cover API Shell permission/response behavior for governance, adapters, jobs, and config, plus config declaration alignment with the runtime surface manifest.

Missing coverage includes assembly-scan failure visibility, naming misclassification, active-probe aggregation, paging consistency, config schema semantics, response cache policy, and an explicit warning when only fallback specs exist.

16. Source and tests

Terminal window
# Inspect concrete projections and the Host's config-spec aggregation.
sed -n '1,560p' \
src/Platform/Operations/BitzOrcas.Platform.Operations.Application/Services/OperationsService.cs
sed -n '1,320p' \
src/Hosts/BitzOrcas.Api/Composition/ConfigDiagnosticRegistration.cs
# Verify runtime-surface and config declaration alignment.
dotnet test tests/BitzOrcas.Architecture.Tests \
--filter FullyQualifiedName~OperationsRuntimeSurfaceTests

Operations overview · Security and GA

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%