Guidance is the coordination module for in-application help content, guided campaigns, and AI-assisted sessions. It owns page/component Markdown guides, platform-default versus tenant-override selection, guided campaigns (Campaign) that organize published content into ordered multi-step guided paths, and mappings from Guidance sessions to AIManage conversations. AIManage still owns models, providers, usage, conversations, and generated messages.
1. Implemented behavior
- unified
GuidanceContentaggregate persistence with soft delete; - RouteKey, optional ComponentId, one RequiredRole, TenantId, language, content version, and Draft/Published state;
- create, revise, publish, delete, list, detail, and contextual queries;
- tenant-component, tenant-page, platform-component, platform-page ranking;
- Published, role, and language filtering on contextual lookup;
- unified tenant-owned
GuidanceAssistantSessionpersistence; - stable SessionId derived from route/component/language;
- a system prompt assembled from page, identity, roles, and the selected guide;
- fail-closed assistant configuration and an explicit missing-workspace error;
- synchronous messages and a handwritten NDJSON stream;
- SqlSugar/EF Core parity foundations for content and sessions;
GuidanceCampaignguided campaigns: ordered multi-step, audience targeting (roles/users/whole-tenant), scheduling, a Draft/Active/Paused/Archived state machine, pre-assignment on activate with implicit assignment on first interaction, server-authoritative progress (idempotent completion and controlled dismissal), and completion-rate analytics.
The module does not currently provide immutable published revisions, history/diff/rollback/unpublish, publication approval, natural-key uniqueness, HTTP ETags, separate assistant permission/feature, prompt refresh/revocation, session TTL, start idempotency/compensation, a versioned stream protocol, a Markdown rendering-security contract, or complete HTTP security evidence.
2. Actual runtime shape
Application sees only the Guidance-owned IAiSessionAdapter. The Infrastructure adapter still references AIManage Application directly and invokes IAIConversationStore plus message handlers. The old claim that Guidance has no direct AIManage dependency applies to Application/Content, not to the complete physical module.
3. HTTP routes
| Method and route | Purpose | Registration |
|---|---|---|
POST /api/v1/guidance/contents | create Draft | generated |
GET /api/v1/guidance/contents | administration list | generated |
GET /api/v1/guidance/contents/{contentId} | content detail | generated |
PUT /api/v1/guidance/contents/{contentId} | revise same row and return to Draft | generated |
POST /api/v1/guidance/contents/{contentId}/publish | transition to Published | generated |
DELETE /api/v1/guidance/contents/{contentId} | soft delete | generated |
GET /api/v1/guidance/contextual | best published guide | generated |
POST /api/v1/guidance/sessions | create/reuse assistant session | generated |
POST /api/v1/guidance/sessions/{sessionId}/messages | synchronous message | generated |
POST /api/v1/guidance/sessions/{sessionId}/messages/stream | NDJSON stream | handwritten |
A separate group of guided-campaign routes (create, list, detail, update, activate/pause/archive, analytics, campaigns visible to the current user, complete step, dismiss) sits under /api/v1/guidance/campaigns. For the full campaign route list, state machine, audience, and progress mechanisms, see Campaigns, assignment, and progress.
Nine non-streaming content/assistant routes are source-generated. The handwritten stream requires authentication and userPolicy, sets no-cache and disables proxy buffering, but calls DisableRequestTimeout without a dedicated maximum stream duration.
4. Create one tenant guide
// A regular tenant supplies its current tenant explicitly to avoid platform-null semantics.var result = await sender.Send(new CreateGuide.Command( RouteKey: "/billing/invoices", ComponentId: null, Title: "Invoice list operations", BodyMarkdown: "1. Select a client.\n2. Verify invoice status.", RequiredRole: "Attorney", TenantId: currentTenantId, LanguageCode: LanguageCode.EnUs), cancellationToken);
// Success creates a Draft; contextual readers cannot see it yet.if (result.IsFailure) return result.Error;
return result.Value!;When a regular tenant omits TenantId, the command uses the current tenant. When the platform tenant omits it, the command creates platform content with TenantId null. The platform tenant can also create content for an explicit tenant. An unknown creation LanguageCode silently normalizes to zh-CN.
5. Contextual selection
RequiredRole is one string. It cannot express any/all roles, permissions, office, plan, feature, user attributes, or deny conditions. Duplicate candidates are legal in the database; the store chooses one through sorting rather than rejecting ambiguous natural keys.
6. Publication lifecycle
Create produces version 1 Draft. Publish mutates it to Published; repeated Publish returns Conflict. Revise updates title/body, increments version, and mutates the same row back to Draft. The live Published content disappears until another Publish. There is no history row or current-published pointer.
Delete loads and guards the aggregate before a global-ID soft delete. The store returns no affected count. A repeated delete normally becomes NotFound at the handler read.
7. Administrative read and end-user read are different security surfaces
Contextual correctly restricts Published, tenant/platform, role, and language. GetById first loads by global primary key, while EnsureCanRead permits TenantId-null platform content for every tenant and checks neither status nor RequiredRole. A user with generic content View who obtains a platform Draft ID may read its complete Markdown body.
List defaults IncludePlatform to true and does not filter RequiredRole, so it can expose platform Draft summaries. GA must separate end-user published-read from content-administration resources, permissions, and DTOs.
8. The assistant is not an automatic content fallback
Content APIs remain usable with AI disabled. Start Session itself fails when Guidance:EnableAiAssistant is false or the workspace is missing; it does not return a content-only session. Clients should fetch Contextual independently and start Assistant only when enabled.
All assistant requests use the guidance/assistant resource. The permission catalog now registers guidance.assistant.use. A missing identity becomes user ID "0" rather than failing closed.
9. Session reuse risks
SessionId contains the user ID plus a route/component/language hash, and lookups bind TenantId and UserId. This prevents straightforward cross-user access. Reuse nevertheless returns the existing AIManage conversation without updating SystemPrompt. A changed guide or revoked role can leave old restricted content in that conversation.
Concurrent Start requests can each create a conversation before competing on the session unique key. There is no explicit compensation between conversation creation and session insertion. If InitialQuestion fails, the session already exists while the command reports failure.
10. Reading path
- Content lifecycle and persistence covers aggregate fields, state, versions, uniqueness, soft delete, and Markdown boundaries.
- Contextual selection and authorization covers ranking, roles, platform/tenant reads, and Draft exposure.
- Campaigns, assignment, and progress covers the campaign state machine, audience and scheduling, user progress, authoritative completion and dismissal, analytics, and persistence.
- Assistant sessions, prompts, and security covers session identity, AIManage integration, stale prompts, privacy, and consistency.
- Streaming, testing, operations, and GA covers NDJSON, faults, capacity, metrics, recovery, and release gates.
11. Commercial GA red lines
- End-user APIs return only Published content allowed by the current audience.
- Draft administration permission is separate from ordinary content read.
- Published revisions are immutable; editing does not remove the live revision.
- Natural keys, published pointers, and concurrent updates have database/ETag guards.
- Markdown HTML, URL, image, CSP, and sanitizer policy is shared across clients.
- Assistant has separate permission, feature, quota, and a required user identity.
- Prompt data minimizes identity/roles and applies sensitive-content policy.
- Guide/role/permission changes refresh or revoke old sessions.
- Conversation/session creation is idempotent, atomic, or compensatable.
- NDJSON uses versioned frames, stable error codes, duration and size bounds.
- An AIManage owner contract enforces workspace, model, usage, and permissions.
- Dual-ORM, HTTP, concurrency, fault, privacy, and recovery evidence passes GA gates.
12. Source navigation
# Generated content and assistant routes plus one handwritten stream route.rg -n "GenerateEndpoint\(|messages/stream|application/x-ndjson" \ src/Platform/Guidance src/Hosts/BitzOrcas.Api/Endpoints/GuidanceEndpointGroup.cs -g '*.cs'
# Campaign aggregate root, state machine, assignment and progress, permissions, and routes.rg -n "GuidanceCampaign|guidance.campaign|CampaignResource" \ src/Platform/Guidance -g '*.cs'
# Current platform-read, identity fallback, unbounded timeout, and AIManage dependency.rg -n "content.TenantId is null|UserId\?\.ToString|DisableRequestTimeout|AIManage.Application" \ src/Platform/Guidance src/Hosts/BitzOrcas.Api -g '*.cs' -g '*.csproj'
# Immutable publication and prompt version should have no matches.rg -n "PublishedRevision|PromptHash|ExpiresAt" \ src/Platform/Guidance -g '*.cs'Back to the module catalog · AIManage module · Authorization module