Owns tenant release notes plus global client-version catalogs, deterministic staged rollout, force-update floors, and upgrade checks.
Primary path at a glance
The coral node marks the module’s central decision or state boundary.
Capability boundary
- Draft, published, and archived release-note lifecycle
- Draft, published, and deprecated app-version lifecycle
- Semantic-version comparison, stable rollout, and force-update decisions
The module explicitly does not own the following: ReleaseManagement publishes metadata and upgrade decisions. It does not build artifacts, upload binaries, operate an app store, or replace package distribution and deployment pipelines.
Code map
| Project | Primary responsibility |
|---|---|
BitzOrcas.Platform.ReleaseManagement.Contracts | Public contracts, ports, DTOs, and events |
BitzOrcas.Platform.ReleaseManagement.Application | Commands, queries, handlers, and application policy |
BitzOrcas.Platform.ReleaseManagement.Infrastructure | Persistence, connectors, and framework adapters |
Source location: src/Platform/ReleaseManagement
Core use cases
CreateReleaseNoteCommandPublishReleaseNoteCommandListPublicReleaseNotesQueryCreateAppVersionCommandPublishAppVersionCommandCheckUpgradeQuery
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 |
|---|---|
ReleaseNoteDto | Tenant release-note projection |
AppVersionDto | Global client-version projection |
IRolloutEvaluator | Deterministic rollout decision seam |
UpgradeCheckResult | Client upgrade decision |
Use-case boundary example
| Field | Value |
|---|---|
| Representative contract | CheckUpgradeQuery |
| Result | Result<UpgradeCheckResult> |
| Review focus | Parse the current version, select only published candidates, bind rollout to a stable client identifier, and evaluate the force-update floor independently of optional rollout. |
Pseudocode
// ① Reject immediately with a stable validation error when parsing fails.if (!SemanticVersion.TryParse(query.CurrentVersion, out var current)) return Result.Failure<UpgradeCheckResult>(ReleaseManagementErrors.AppVersionVersionInvalid);// ② Candidates filter by platform and channel over published rows, sorted high to low.var published = await store.ListPublishedByPlatformAsync( query.Platform, query.Channel, cancellationToken);if (published.Count == 0) return NoUpdate;var latest = published[0];// ③ Force-update floors and optional rollout stay independent decisions.var forceUpdate = !string.IsNullOrWhiteSpace(latest.MinForceUpdateVersion) && SemanticVersion.TryParse(latest.MinForceUpdateVersion, out var minRequired) && current.IsLowerThan(minRequired);var rolloutHit = evaluator.IsHit(request.ActorKey, latest.RolloutPercentage);// ④ A stable ActorKey hash pins each device or user to one rollout cohort.return forceUpdate || rolloutHit ? Result.Success(new UpgradeCheckResult(forceUpdate, latest.Version, latest.BuildNumber, rolloutHit, latest.DownloadUrl, latest.ReleaseNoteKey)) : NoUpdate;NoUpdate is not a static factory on the type; it is a private success result held inside CheckUpgradeQueryHandler representing the neutral “nothing to install” outcome.
Integration relationships
Release notes are tenant-scoped, while AppVersionCatalogRecord is global. Upgrade checks use a stable client identifier for percentage rollout and return metadata and an HTTPS download URL; artifact integrity and hosting remain external concerns.
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
Management routes require release permissions and the default-off release.manage feature. Public release-note and upgrade-check projections exclude draft records. Production download URLs require HTTPS, with local/private exceptions reserved for restricted development environments.
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
Rollout is deterministic for the same version and client identifier, so clients do not oscillate between cohorts. Version parsing accepts v-prefixed three-part versions and date-style releases but rejects prerelease/build suffixes.
| 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 | semantic-version accepted and rejected forms | unit + integration |
| 2 | zero, full, and stable partial rollout | integration/contract |
| 3 | force-update floor and deprecated versions | integration/contract |
| 4 | tenant release notes versus global app versions | 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/ReleaseManagement -g '*.cs'
# Inspect cross-layer references; callers must not depend on another module's Infrastructure.rg -n "ProjectReference|PackageReference" src/Platform/ReleaseManagement -g '*.csproj'
# Find unfinished or elided implementation; expect no matches.rg -n "TODO|FIXME|// \.\.\." src/Platform/ReleaseManagement -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