Skip to content
bitzorcas
中EN

Concept

Announcements

Owns tenant announcements from draft through publication, withdrawal, and archive, including audience targeting and idempotent per-user read state.

Last updated

Owns tenant announcements from draft through publication, withdrawal, and archive, including audience targeting and idempotent per-user read state.

Primary path at a glance

Manager creates a draft

Normalize audience and effective window

Publish immutable content

Filter with trusted user context

Record an idempotent read fact

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

Capability boundary

  • Draft, publish, withdraw, and archive transitions
  • All-user, role, and department audience filters
  • Unique per-user read facts and management read counts

The module explicitly does not own the following: Announcements decides announcement lifecycle and visibility. Identity and organization membership remain authoritative for the current user, roles, and departments; Notifications remains the owner of out-of-band delivery.

Code map

ProjectPrimary responsibility
BitzOrcas.Platform.Announcements.ContractsPublic contracts, ports, DTOs, and events
BitzOrcas.Platform.Announcements.ApplicationCommands, queries, handlers, and application policy
BitzOrcas.Platform.Announcements.InfrastructurePersistence, connectors, and framework adapters

Source location: src/Platform/Announcements

Core use cases

  • CreateAnnouncementCommand
  • UpdateAnnouncementCommand
  • PublishAnnouncementCommand
  • WithdrawAnnouncementCommand
  • ArchiveAnnouncementCommand
  • ListMyAnnouncementsQuery
  • MarkAnnouncementReadCommand

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
AnnouncementDtoManagement lifecycle projection
AnnouncementViewAudience-filtered user projection
IAnnouncementStoreLifecycle and visibility store port
IAnnouncementReadStoreIdempotent read-state port

Use-case boundary example

FieldValue
Representative contractPublishAnnouncementCommand
ResultResult<AnnouncementDto>
Review focusPublish only a draft, validate the effective window against the server clock, and persist the new concurrency version before it becomes visible.

Pseudocode

PublishAnnouncementCommand pseudocode
var existing = await store.FindByIdAsync(command.AnnouncementId, cancellationToken);
// ① Publish is legal only from Draft and every decision uses the server clock.
if (existing is null) return AnnouncementErrors.NotFound;
if (existing.Status != AnnouncementStatus.Draft.Name) return AnnouncementErrors.InvalidTransition;
var now = clock.UtcNow;
var effectiveAt = existing.EffectiveAt ?? now;
// ② A window that expired or starts after expiry must be rejected.
if (existing.ExpiresAt is not null
&& (existing.ExpiresAt <= effectiveAt || existing.ExpiresAt <= now))
return AnnouncementErrors.InvalidEffectiveWindow;
// ③ The transition builds a new snapshot via a record with-expression; only a saved
// snapshot becomes visible to user-facing queries.
var published = existing with
{
Status = AnnouncementStatus.Published.Name,
PublishedAt = now,
EffectiveAt = effectiveAt,
WithdrawnAt = null,
};
return await store.SaveAsync(published, cancellationToken);

Integration relationships

The user-facing query resolves trusted department IDs through the organization-member repository and combines them with the current user’s roles. The module does not accept department membership from request input.

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 announcement permissions and the default-off announcements.manage feature. User routes derive tenant, user, roles, and departments from trusted context and fail closed when the announcement is outside the audience or active window.

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

Draft updates use an explicit concurrency version. Published content is immutable, withdrawal takes effect immediately, archive is terminal, and the read store must enforce one read fact per tenant, user, and announcement.

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
1legal lifecycle and stale concurrency versionunit + integration
2role/department audience filtering fails closedintegration/contract
3effective, expiry, and server-clock boundariesintegration/contract
4idempotent reads and management read countintegration/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/Announcements -g '*.cs'
# Inspect cross-layer references; callers must not depend on another module's Infrastructure.
rg -n "ProjectReference|PackageReference" src/Platform/Announcements -g '*.csproj'
# Find unfinished or elided implementation; expect no matches.
rg -n "TODO|FIXME|// \.\.\." src/Platform/Announcements -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%