Documents provides multi-tenant knowledge bases, category trees, versioned content, publishing, tags, access-rule data, and whiteboards. Its aggregate and snapshot model is useful for back-office knowledge management. Resource access, indexing, binary conversion, and real-time collaboration, however, are not complete product loops yet.
1. What the module owns
Documents turns editable text into a content asset with a lifecycle:
- knowledge bases define content spaces;
- categories and parent documents provide two independent navigation trees;
Documentstores current content, metadata, state, and complete version snapshots;- publish, unpublish, archive, and rollback are aggregate operations;
- tags, access rules, knowledge-base members, and whiteboard members are embedded JSON collections;
- a QueryShape read model serves details, lists, and trees;
- internal search, cache, file, cloud-drive, and collaboration ports provide extension seams.
Binary asset security belongs to Files, shared indexing belongs to Search, and comment threads belong to Comments. The similarly named internal Documents services do not replace those platform modules.
2. Current architecture
| Project | Responsibility | Boundary |
|---|---|---|
BitzOrcas.Platform.Documents.Contracts | Aggregates, DTOs, governance catalogs, read/connector ports, events | No ORM or vendor SDK types |
BitzOrcas.Platform.Documents.Application | Commands, queries, handlers, mapping, in-memory search/cache/file services | Framework abstractions plus Contracts |
BitzOrcas.Platform.Documents.Infrastructure | QueryShape reads, collaboration defaults, cloud adapter, index subscriber | Application/Contracts plus connectors |
The module governance marker allows dependencies on Authorization, Files, and Search. An allowed dependency describes architecture intent; it does not prove an end-to-end integration exists.
3. Relationships are not referential integrity
These are domain identifiers, not universally validated foreign keys. CreateDocument does not verify the knowledge base, category, or parent document, nor that a category belongs to the requested knowledge base. CreateWhiteboard does not validate its optional KnowledgeBaseId. CreateKnowledgeBase does not validate ParentId or cycles. Category creation only validates that an existing parent category belongs to the same knowledge base.
4. Generated HTTP surface
Documents and versions
| Method and route | Use case | Generic action |
| ------------------------------------ | ----------------------------------- | ------------------- | --------------------- | ------ |
| POST /api/v1/documents/ | Create Draft and 1.0.0 | Create |
| GET /api/v1/documents | Tenant list, at most 100/page | Authentication only |
| GET /api/v1/documents/{id} | Full detail | Authentication only |
| PUT /api/v1/documents/{id} | Update metadata | Update |
| PUT /api/v1/documents/{id}/content | Create a Patch snapshot | Update |
| GET .../{id}/versions[/{version}] | Version history/detail | Authentication only |
| GET .../{id}/versions/diff | Simplified line diff | Authentication only |
| POST .../{version}/rollback | Create a new Patch from old content | Update |
| POST .../{id}/publish | unpublish | archive | Lifecycle transitions | Update |
| POST/DELETE .../tags | Add/remove tags | Update |
| POST/DELETE .../access-rules | Mutate ACL data | Update |
| DELETE /api/v1/documents/{id} | Soft delete | Delete |
“Authentication only” means the message lacks IAuthorizedRequest. The generated endpoint is still protected by the default authentication convention, but it does not enter the framework resource/action decision.
Knowledge bases, categories, and whiteboards
| Resource | Writes | Reads | Current caveat |
|---|---|---|---|
| Knowledge base | /api/v1/knowledgebases, /{id}, members | detail/list/tree | Set-access command has no endpoint; reads ignore members |
| Category | /api/v1/documents/categories/, /{id} | /tree | No category permission definitions; no cycle guard |
| Whiteboard | /api/v1/whiteboards, data, members, archive/delete | detail/list | No whiteboard permission definitions; members are not authorization |
Search and suggestion messages implement IAuthorizedRequest but have no [GenerateEndpoint]. Collaboration, document-file, and cloud-drive ports likewise have no public Documents route.
5. Document lifecycle and versions
Every version stores the complete content, SHA-256, UTF-8 byte count, change summary, creator/time, and publish marker. Regular updates only increment Patch. Rollback never rewinds the number or removes later history; it creates a new Patch containing an older snapshot.
Archived blocks content and metadata changes, publish/unpublish, and adding tags or access rules. Removal rules are inconsistent: an archived document can still lose tags and access rules. Archived therefore does not mean “fully immutable” in the current implementation.
6. Create, edit, and publish example
public async Task PublishArticleAsync( HttpClient api, string knowledgeBaseId, CancellationToken cancellationToken){ // Creation stores current content and the initial 1.0.0 snapshot together. using var create = await api.PostAsJsonAsync( "/api/v1/documents/", new { knowledgeBaseId, categoryId = (string?)null, parentId = (string?)null, title = "Order cancellation runbook", description = "Steps for support agents", content = "# Cancellation\n\nCheck payment state first.", contentType = "Markdown", languageCode = "en-US", allowComments = true, allowCollaboration = false }, cancellationToken); create.EnsureSuccessStatusCode(); var document = await create.Content.ReadFromJsonAsync<DocumentDto>(cancellationToken);
// Each edit writes another complete snapshot and increments Patch. using var update = await api.PutAsJsonAsync( $"/api/v1/documents/{document!.DocumentId}/content", new { content = "# Cancellation\n\n1. Check payment.\n2. Record the reason.", changeSummary = "Add audit requirement" }, cancellationToken); update.EnsureSuccessStatusCode();
// Publish has no expectedVersion, so the client cannot close a concurrent edit race. using var publish = await api.PostAsync( $"/api/v1/documents/{document.DocumentId}/publish", content: null, cancellationToken); publish.EnsureSuccessStatusCode();}
public sealed record DocumentDto(string DocumentId, string CurrentVersion);A production editor should add an explicit expected version and a database-enforced concurrency token. A read-before-write comparison on the client only narrows the race window.
7. Effective security matrix
| Layer | Present | Gap |
|---|---|---|
| Authentication | Default endpoint authentication | Does not establish resource visibility |
| Generic authorization | 29 write commands implement IAuthorizedRequest | Generated GET messages do not; search has no endpoint |
| Permission catalog | Six document and four knowledge-base permissions | Category/whiteboard definitions are absent |
| Action mapping | Create/update/delete/view actions | Publish, tag, and ACL commands use Update; publish/manage constants are bypassed |
| ACL/member data | Persisted and returned | Not applied by handlers or read model |
| Feature | documents.manage, disabled by default | No runtime feature decision found in Documents source |
| Tenant | Main resources use User.TenantId | No EffectiveTenantId; collaboration-session reads omit tenant entirely |
Do not market private knowledge bases, member-only visibility, document ACL enforcement, or active package gating until those gaps are closed with end-to-end contract tests.
8. Persistence and counts
The unified aggregates map directly to tenant-aware, soft-delete tables: DocsDocument, DocsKnowledgeBase, DocsDocumentCategory, DocsWhiteboards, and DocsCollaborationSession. Versions, tags, ACLs, and member collections are JSON columns in their owner row.
Knowledge-base and category aggregates contain document-count mutation methods, but Create/Delete Document handlers do not call them. Knowledge-base delete separately queries the document repository. Category delete trusts its potentially stale count and does not check child categories. Treat these counts as denormalized display data, not referential integrity.
9. Capability status
| Capability | Current conclusion |
|---|---|
| Tenant-scoped document CRUD, snapshots, lifecycle | Usable after adding resource access and concurrency controls |
| Knowledge-base/category trees | Navigation-ready; dirty parents and cycles need protection |
| Tags, ACLs, members | Persistence is present; ACL/member enforcement is not |
| Versioned whiteboard JSON | Detects a stale request version; lacks JSON/size and DB concurrency guards |
| Search | Internal first-200 in-memory search, no HTTP route, not the shared Search engine |
| Document cache | Registered but unused by business handlers |
| Document upload/download | Internal stub; upload does not store bytes and PDF/Word are not conversions |
| Real-time collaboration | Single-process memory plus logging-only hubs |
| Cloud drive | Neutral port and adapter exist; no use-case or composition loop |
10. Reading path
- Content, versions, publishing, and rollback
- Knowledge bases, categories, members, and access rules
- Reads, search, cache, files, and cloud drives
- Whiteboards, sessions, and real-time boundaries
- Testing, observability, and production gates
Related: Authorization, Multitenancy, Files, and Auditing.
11. Source audit commands
# The generated HTTP surface.rg -n '^\[GenerateEndpoint' src/Platform/Documents -g '*.cs'
# Compare authorized messages with read queries.rg -n 'IAuthorizedRequest|IQuery<Result' \ src/Platform/Documents/BitzOrcas.Platform.Documents.Application -g '*.cs'
# These methods currently remain in models/tests rather than complete handler paths.rg -n 'HasAccess\(|IncrementDocumentCount\(|SetStorage\(|IncrementViewCount\(' \ src/Platform/Documents tests -g '*.cs'