Skip to content
bitzorcas
中EN

Concept

Commercial Distribution

Owns private-package entitlements and short-lived feed credentials for commercial distribution, with revocation-aware credential introspection.

Last updated

Owns private-package entitlements and short-lived feed credentials for commercial distribution, with revocation-aware credential introspection.

Primary path at a glance

Grant scoped entitlement

Issue 1-168 hour credential

Feed sends Basic Authentication

Unprotect token and re-query entitlement

Return active scope or one inactive result

The coral node marks the module’s central decision or state boundary.

Capability boundary

  • Tenant/customer/product/channel/package-prefix entitlement scope
  • Grant and irreversible revoke lifecycle
  • Protected short-lived feed tokens and fail-closed introspection

The module explicitly does not own the following: A package entitlement grants access to a product, version channel, and package prefix. It is neither a runtime license nor a tenant feature, and the module is not itself a NuGet server or credential provider.

Code map

ProjectPrimary responsibility
BitzOrcas.Platform.CommercialDistribution.ContractsPublic contracts, ports, DTOs, and events
BitzOrcas.Platform.CommercialDistribution.DomainAggregates, invariants, and pure domain rules
BitzOrcas.Platform.CommercialDistribution.ApplicationCommands, queries, handlers, and application policy
BitzOrcas.Platform.CommercialDistribution.InfrastructurePersistence, connectors, and framework adapters

Source location: src/Platform/CommercialDistribution

Core use cases

  • GrantPackageEntitlementCommand
  • RevokePackageEntitlementCommand
  • ListPackageEntitlementsQuery
  • GetPackageEntitlementQuery
  • IssueFeedTokenCommand
  • ValidateFeedTokenCommand

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 typeWhat to inspect
PackageEntitlementEntitlement aggregate and invariants
PackageEntitlementSummaryManagement read model
IPackageEntitlementCommandStoreAggregate persistence port
ICommercialFeedTokenProtectorCredential protection seam

Use-case boundary example

FieldValue
Representative contractValidateFeedTokenCommand
ResultResult<FeedTokenValidationResult>
Review focusTreat introspection as a fail-closed gateway contract: reveal no failure reason and always re-query current entitlements after validating the token envelope.

Pseudocode

ValidateFeedTokenCommand pseudocode
// ① Any envelope or username failure becomes the same inactive result.
if (!tokenProtector.TryUnprotect(command.Token, out var payload)
|| payload is null
|| payload.ExpiresAt <= clock.UtcNow
|| command.UserName != $"feed:{payload.CustomerId}")
return FeedTokenValidationResult.Inactive;
// ② Re-query current rights so revocation beats an unexpired token.
var rights = await entitlements.FindByCustomerAsync(
payload.TenantId, payload.CustomerId, payload.ProductId, cancellationToken);
// ③ A read-store failure also closes access without disclosing internals.
if (rights.IsFailure) return FeedTokenValidationResult.Inactive;
// ④ Package prefixes and version channels aggregate from the current entitlement rows.
return rights.Value.Count == 0
? FeedTokenValidationResult.Inactive
: new FeedTokenValidationResult(true,
payload.CustomerId, payload.ProductId, payload.ExpiresAt,
rights.Value.Select(item => item.PackagePrefix)
.Distinct(StringComparer.Ordinal)
.OrderBy(value => value, StringComparer.Ordinal).ToArray(),
rights.Value.Select(item => item.VersionChannel)
.Distinct(StringComparer.Ordinal)
.OrderBy(value => value, StringComparer.Ordinal).ToArray());

Integration relationships

A feed gateway calls the unauthenticated introspection endpoint behind its own network boundary. The endpoint validates the protected payload and then reloads active entitlements so a revocation takes effect without waiting for token expiry.

This page describes the module boundary. See private feeds and customer authentication for repository, gateway, client authentication, and revocation operations, and license issuance for the separation between package rights and runtime licenses.

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 operations require commercial-distribution permissions and the default-off feed-management feature. Token tampering, expiry, username mismatch, entitlement revocation, and store failure all return the same inactive response without disclosing the reason.

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

Grant is idempotent for the same entitlement ID and scope and conflicts on a changed scope. Revocation is irreversible. QueryShape supplies ORM-neutral list/detail reads, while Data Protection protects token integrity and confidentiality.

SituationRequired behavior
Validation, not found, conflict, or forbiddenReturn typed Result/Error data and let the shared mapper produce Problem Details
Adapter or critical state unavailableFail closed for security/high-value work; mark read-only degradation explicitly
Duplicate request or eventUse a stable idempotency key with a database uniqueness constraint as the final guard
External timeoutPropagate cancellation, record redacted diagnostics, and retry only safe operations with bounds

Test matrix

#Required scenarioSuggested level
1idempotent grant and conflicting replayunit + integration
2revoke then introspect an unexpired tokenintegration/contract
3tamper, expiry, username mismatch, and store outageintegration/contract
4EF/SqlSugar-free contracts and application layersintegration/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:

Terminal window
# 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/CommercialDistribution -g '*.cs'
# Inspect cross-layer references; callers must not depend on another module's Infrastructure.
rg -n "ProjectReference|PackageReference" src/Platform/CommercialDistribution -g '*.csproj'
# Find unfinished or elided implementation; expect no matches.
rg -n "TODO|FIXME|// \.\.\." src/Platform/CommercialDistribution -g '*.cs'

Extension and release checklist

  1. Is the new capability expressed through explicit Contracts, permissions, and a feature key?
  2. Can persistence, connectors, and providers be replaced through narrow adapters?
  3. Does production still resolve a Null/Unavailable default, and could health checks report a false green?
  4. Is there evidence for transaction, event, cache-invalidation, audit, and background-job failure/retry behavior?
  5. Did bilingual docs, diagrams, contract tests, and runbooks change with the code?

Back to module catalog · Module dependency diagram · Add a module guide

100%

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