Documents cannot be accepted for production merely because CRUD returns 200. Version history expands the aggregate, access data is not enforced, trees can contain invalid references, search stops at 200 candidates, and collaboration is process memory. Tests and operations must target those risks.
1. Existing test baseline
| Test file | Current Facts | Focus |
|---|---|---|
DocumentTests | 21 | Creation, versions, lifecycle, tags, rules, view count |
KnowledgeBaseTests | 13 | Members, counts, restore |
DocumentCategoryTests | 10 | Names, parents, counts |
WhiteboardTests | 14 | Data versions, archive, members |
CollaborationSessionTests | 11 | Join, leave, end, restore |
DocumentSearchServiceTests | 13 | Scoring, filters, paging, suggestions |
| JSON/QueryShape/filters/recycle bin | 18 | Persistence shape and tenant reads |
| Application read handlers | 4 | Trusted tenant and read-store delegation |
| Infrastructure architecture | 8 | Dependency and persistence rules |
This is useful local semantic coverage. It does not establish HTTP authorization, ORM parity, concurrency, CAP publishing, cache correctness, database indexes, or multi-instance collaboration.
2. Risk-driven pyramid
Aggregate coverage
- every Document state crossed with every mutation;
- SHA-256, UTF-8 byte count, and Patch progression;
- forward-only rollback and unpublish/version-marker semantics;
- fail-closed restoration of embedded JSON;
- orphan and cycle behavior in trees;
- the intentionally simplified diff limits.
Application and authorization contracts
- method, route, authentication, rate limit, and timeout for every generated endpoint;
- every write message derives a permission present in its catalog;
- every read message explicitly chooses authorization behavior;
- User.TenantId versus EffectiveTenantId;
- member, ACL, public, creator, and administrator decision tables;
- KB/category/parent integrity on create and authoritative references on delete.
Persistence and concurrency
- SqlSugar/EF Core parity for fields, JSON, soft delete, and tenant behavior;
- category unique-index races;
- two content updates cannot lose a version;
- two whiteboard updates with one expected version cannot both commit;
- large VersionsJson/Canvas Data performance and migration;
- knowledge-base/category count reconciliation.
3. Authorization contract example
[Theory][InlineData("tenant-a", "tenant-b", false)][InlineData("tenant-a", "tenant-a", true)]public async Task Detail_Must_Enforce_Tenant_And_Resource_Access( string callerTenant, string documentTenant, bool sameTenant){ // Arrange a private document without an allowed member. await fixture.SeedDocumentAsync( documentTenant, "doc-secret", isPublic: false, members: []);
using var client = fixture.CreateAuthenticatedClient( callerTenant, userId: "outsider"); // Act as either a cross-tenant caller or a same-tenant outsider. using var response = await client.GetAsync("/api/v1/documents/doc-secret");
// Cross-tenant access hides existence; same-tenant outsider is forbidden by target ACL. response.StatusCode.ShouldBe( sameTenant ? HttpStatusCode.Forbidden : HttpStatusCode.NotFound);}This is the target security contract. The same-tenant case fails against the current implementation and should drive the policy integration red-to-green.
4. Event and cache testing
Define the producer before claiming index synchronization:
The current subscriber suppresses failures and Index/Delete are no-ops. A consumed message is not evidence of index consistency. Test duplicate EventId, out-of-order update/delete, unavailable index, rebuild behavior, ACL revocation, cache invalidation, and poison messages.
5. Observability
Never log full Content, Canvas Data, members, access rules, search terms, or cloud-drive download URLs. Use stable low-cardinality dimensions such as module/use_case, result category, hashed tenant, content type/status, lifecycle transition, and dependency.
Recommended metrics:
documents_command_duration_seconds{use_case,result};documents_version_snapshot_bytesand versions per document;documents_authorization_denied_total{resource,operation};- search candidates and truncation totals;
- index lag;
- invalid tree references by kind;
- active collaboration sessions and rejected/replayed operations;
- count-reconciliation deltas.
6. Runbook
| Signal | Likely cause | First response |
|---|---|---|
| Version failures increase | Concurrency, large row, database locking | Inspect version count/row size; do not blindly retry |
| Detail P95 grows | includeVersions ignored, history growth | Sample VersionsJson and switch to lightweight projection |
| Tree timeout/stack error | Parent cycle or extreme depth | Freeze hierarchy writes; export and run cycle detection |
| Search suddenly returns zero | Read failure cached as empty | Purge search keys, check DB, fix failure caching |
| Search misses content | Candidate set exceeds 200 | Compare tenant document count; move to real index |
| Member/tenant data leak | Missing query policy | Disable route at gateway, audit access, apply policy |
| Whiteboard conflicts spike | Full snapshots or DTO conversion | Check client version parsing and retry behavior |
| Collaboration loses edits | Restart or node switch | The current memory service cannot recover; stop production use |
7. Reconciliation
Run periodic read-only checks for stored versus actual document counts, orphan category/parent/knowledge-base identifiers, cross-KB category references, cycles, CurrentVersion absent from VersionsJson, current content/hash mismatches, invalid publish metadata, and active children beneath soft-deleted parents.
-- Confirm generated schema and soft-delete names before running in production.-- Compare the denormalized value with an authoritative count of live documents.SELECT kb.Id, kb.DocumentCount, COUNT(doc.Id) AS ActualCountFROM DocsKnowledgeBase kbLEFT JOIN DocsDocument doc ON doc.TenantId = kb.TenantId AND doc.KnowledgeBaseId = kb.Id AND doc.IsDeleted = 0WHERE kb.IsDeleted = 0GROUP BY kb.Id, kb.DocumentCountHAVING kb.DocumentCount <> COUNT(doc.Id);8. Capacity and retention
Each version stores full content, so storage is approximately content size multiplied by version count, with large JSON potentially rewritten on every edit. Measure real P50/P95/P99 document size and history length before rollout.
Evolution options include keeping current content in Document while moving snapshots to a separate table/object store, hot/cold history tiers, immutable FileAssets for large binaries, and a policy-driven retention/legal-hold model. Never prune history without audit, export, and legal-retention decisions.
9. GA blockers
- Every public read/write path uses one resource-access policy; ACL/member data is enforced.
- Category and whiteboard permissions exist; Publish/Manage mapping is explicit.
-
documents.manageis runtime-enforced or removed from product claims. - Create paths validate references and cycles.
- Counts use authoritative queries or a reconciled consistency mechanism.
- Document and whiteboard writes have database concurrency protection.
- includeVersions=false uses a lightweight projection.
- Search is complete, does not cache dependency failure as empty, and is XSS-safe.
- Binary documents use Files; no mislabeled PDF/Word response remains.
- Exposed collaboration replaces memory/null hubs with tenant-safe durable scale-out.
- Collaboration-session reads include tenant predicates.
- Event production, idempotent indexing, retries, and rebuilds have contract tests.
- Dual-ORM, HTTP, permission, tenant, backup/restore, concurrency, and capacity suites pass.
Character-level diff, Major/Minor policies, cloud-drive synchronization, conversion, offline CRDT, and recommendation analytics can follow after these blockers.
10. Verification commands
# Run focused domain and service semantics first.dotnet test tests/BitzOrcas.Unit.Tests/BitzOrcas.Unit.Tests.csproj \ --filter 'FullyQualifiedName~BitzOrcas.Unit.Tests.Docs'dotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj \ --filter 'FullyQualifiedName~Documents'dotnet test tests/BitzOrcas.Architecture.Tests/BitzOrcas.Architecture.Tests.csproj \ --filter 'FullyQualifiedName~DocumentsInfrastructureArchitectureTests'
# Sweep for model methods and development defaults that still lack production paths.# These sweeps expose methods/defaults that still lack a complete product path.rg -n 'HasAccess\(|SetStorage\(|IncrementViewCount\(' src tests -g '*.cs'rg -n 'default-tenant|NullRealtimeCollaborationHub|NullWhiteboardCollaborationHub' \ src/Platform/Documents -g '*.cs'