Owns the runtime-license control plane: request, four-eyes review, durable asynchronous signing, download, and revocation.
Primary path at a glance
The coral node marks the module’s central decision or state boundary.
Capability boundary
- Immutable license request facts and review state
- Four-eyes approval and control-plane tenant boundary
- Durable signing/revocation operations and signed-envelope download
The module explicitly does not own the following: LicenseManagement issues signed envelopes; runtime enforcement belongs to the licensing pipeline and product composition. Private keys stay in LicenseSigner, KMS, or HSM and must never enter this module’s database or API process.
Code map
| Project | Primary responsibility |
|---|---|
BitzOrcas.Platform.LicenseManagement.Contracts | Public contracts, ports, DTOs, and events |
BitzOrcas.Platform.LicenseManagement.Application | Commands, queries, handlers, and application policy |
BitzOrcas.Platform.LicenseManagement.Infrastructure | Persistence, connectors, and framework adapters |
Source location: src/Platform/LicenseManagement
Core use cases
CreateRuntimeLicenseCommandApproveRuntimeLicenseCommandRejectRuntimeLicenseCommandIssueRuntimeLicenseCommandRevokeRuntimeLicenseCommandDownloadRuntimeLicenseQueryGetLicenseSignerReadinessQuery
When reading a use case, start with its request contract and permission declaration, follow how the handler coordinates an aggregate or port, then inspect post-commit events, cache changes, and audit.
Key models and contracts
| Key type | What to inspect |
|---|---|
RuntimeLicenseIssuance | Control-plane aggregate |
IRuntimeLicenseIssuanceCommandStore | Atomic command and lease port |
ILicenseEnvelopeSigner | Remote signing boundary |
ILicenseManagementControlPlaneTenantPolicy | Dedicated tenant guard |
Use-case boundary example
| Field | Value |
|---|---|
| Representative contract | IssueRuntimeLicenseCommand |
| Result | Result<RuntimeLicenseIssuanceDto> |
| Review focus | Adjudicate the control-plane tenant first, then freeze the issuance request into a persistent queue by idempotency key; never sign inline in the HTTP request. |
Pseudocode
// ① A missing idempotency key is rejected; RequireActor adjudicates IsTrustedTenant inside.if (string.IsNullOrWhiteSpace(command.IdempotencyKey) || command.IdempotencyKey.Length > 128) return LicenseManagementErrors.IdempotencyKeyRequired;var actor = LicenseManagementApplicationSupport.RequireActor(currentUser, tenantPolicy);if (actor.IsFailure) return Result<RuntimeLicenseIssuanceDto>.Failure(actor.Error);// ② An already-Issued aggregate reads back from the read model, so repeats are idempotent.var found = await store.FindAsync(command.LicenseId, cancellationToken);if (found.IsFailure) return Result<RuntimeLicenseIssuanceDto>.Failure(found.Error);var issuance = found.GetValueOrThrow();if (issuance.Status == RuntimeLicenseIssuanceStatus.Issued) return await readModels.GetAsync(issuance.LicenseId, cancellationToken);// ③ QueueIssuance freezes the first request into a persistent queue; repeats get false.var queued = issuance.QueueIssuance( actor.GetValueOrThrow(), clock.UtcNow, command.IdempotencyKey);if (queued.IsFailure) return Result<RuntimeLicenseIssuanceDto>.Failure(queued.Error);if (!queued.GetValueOrThrow()) return await readModels.GetAsync(issuance.LicenseId, cancellationToken);// ④ SaveAsync persists the aggregate and its embedded signing operation together.await store.SaveAsync(issuance, cancellationToken);return await readModels.GetAsync(issuance.LicenseId, cancellationToken);Integration relationships
The API queues signing work; RuntimeLicenseSigningWorker claims bounded leases and calls ILicenseEnvelopeSigner. The remote result is accepted only when its payload is unchanged and its signature verifies against the configured public key.
See license issuance for the full control-plane state machine, LicenseSigner network boundary, key custody, and failure drills. Runtime verification and degradation policy are covered by runtime licensing.
Cross-module collaboration follows these directions:
- Callers depend on this module’s public Contracts or a narrow port.
- State changes propagate through versioned integration events.
- Database, messaging, cache, file, and vendor SDK details stay in Infrastructure.
- The composition root registers fail-closed defaults before production adapters.
Security, tenancy, and privacy
Every operation must pass LicenseManagement:ControlPlane:TenantId before touching a store. The requester cannot approve the same request, routes use operation-specific permissions, downloads expose only signed envelopes and SHA-256, and signer failure closes the path.
Every tenant-scoped write persists TenantId, and list/detail paths share one authorization boundary. Logs, events, and audit retain only the minimum fields needed for diagnosis.
Failure semantics, idempotency, and observability
Issue and revoke use durable operations with idempotency keys, leases, bounded retries, and terminal failure. Worker completion validates that the aggregate and operation still match before committing the envelope.
| Situation | Required behavior |
|---|---|
| Validation, not found, conflict, or forbidden | Return typed Result/Error data and let the shared mapper produce Problem Details |
| Adapter or critical state unavailable | Fail closed for security/high-value work; mark read-only degradation explicitly |
| Duplicate request or event | Use a stable idempotency key with a database uniqueness constraint as the final guard |
| External timeout | Propagate cancellation, record redacted diagnostics, and retry only safe operations with bounds |
Test matrix
| # | Required scenario | Suggested level |
|---|---|---|
| 1 | control-plane tenant mismatch before store access | unit + integration |
| 2 | requester cannot self-approve | integration/contract |
| 3 | signer payload mutation or invalid signature | integration/contract |
| 4 | lease expiry, retry, idempotency, and terminal failure | integration/contract |
Keep one universal red line under test: no identifier, cache key, event, or query condition from tenant A may let tenant B read or mutate data.
Source navigation and change checks
Run from the BitzOrcasVNext repository root:
# List public types and confirm that new contracts belong to the module boundary.rg -n "^public (sealed |abstract |static |partial )*(record|class|interface|enum)" src/Platform/LicenseManagement -g '*.cs'
# Inspect cross-layer references; callers must not depend on another module's Infrastructure.rg -n "ProjectReference|PackageReference" src/Platform/LicenseManagement -g '*.csproj'
# Find unfinished or elided implementation; expect no matches.rg -n "TODO|FIXME|// \.\.\." src/Platform/LicenseManagement -g '*.cs'Extension and release checklist
- Is the new capability expressed through explicit Contracts, permissions, and a feature key?
- Can persistence, connectors, and providers be replaced through narrow adapters?
- Does production still resolve a Null/Unavailable default, and could health checks report a false green?
- Is there evidence for transaction, event, cache-invalidation, audit, and background-job failure/retry behavior?
- Did bilingual docs, diagrams, contract tests, and runbooks change with the code?
Back to module catalog · Module dependency diagram · Add a module guide