AdvisoryAI Chat Interface
Sprint: SPRINT_20260107_006_003 Task CH-016 Status: Active Last Updated: 2026-10-20 (action role binding, SPRINT_20261006_114 PCD-904)
The AdvisoryAI Chat Interface provides a conversational experience for security operators to investigate vulnerabilities, understand findings, and take remediation actions—all grounded in internal evidence with citations.
Overview
The chat interface enables:
- Multi-turn conversations about vulnerabilities, components, and security posture
- Grounded responses with citations to internal evidence (SBOMs, VEX, attestations)
- Action proposals for risk approval, quarantine, and VEX creation
- Streaming responses for real-time feedback
Controlled Gateway and Budgets
- Chat Gateway enforces Authority auth, quotas, and token budgets per user/org.
- Inference boundary uses the configured in-house/OpenAI-compatible local model gateway for production-like question answering. The gateway must be reachable through
GET /health,GET /v1/models, andPOST /v1/chat/completions. - Grounding contract requires structured JSON responses with cited claims. Citation IDs are stable
CIT-##-<hash>values derived from evidence, Knowledge Search, and Unified Search context. - Fail-closed behavior returns controlled runtime unavailability when the model is missing, unhealthy, returns invalid JSON, cites unknown evidence, or emits uncited factual claims. Chat does not return prompt previews, deterministic placeholder answers, or cloud fallback output in production-like mode.
- Settings overrides: quotas and tool allowlists are configurable via UI/CLI settings; env values are defaults.
- Doctor action reports quota/tool limits and last denial for troubleshooting.
- Scrubber removes secrets and PII using regex + entropy filters + allowlists.
- Tool gating runs policy checks before any tool invocation; read-only by default.
Sanctioned Tools (v1)
- Read-only:
vex.query,sbom.read,scanner.findings.topk. - Action tools require explicit confirmation plus policy allow.
API Reference
Endpoint Families and Migration Timeline
- Canonical chat surface:
/api/v1/chat/* - Legacy compatibility surface:
/v1/advisory-ai/conversations* - Legacy sunset date (UTC): December 31, 2026
- Legacy responses emit migration headers:
Deprecation: trueSunset: Thu, 31 Dec 2026 23:59:59 GMTLink: </api/v1/chat/query>; rel="successor-version"
Tenant binding
Every conversation, chat, consent and run route takes the tenant from the caller’s authenticated identity: the stellaops:tenant claim of the gateway-signed identity envelope. No request body or query value names the tenant, and a principal without a tenant receives 400 (tenant_missing) before anything is read or written; there is no default tenant fallback. A conversation is addressed by tenant and ID: a conversation ID that belongs to another tenant returns 404 with no body, the same answer as an unknown ID, and leaves that conversation unchanged. The assistant turn is assembled for the caller’s tenant only.
Action role binding
The roles that decide whether a proposed action needs approval come only from the caller’s verified identity: the roles of the gateway-signed identity envelope. The gateway fills them from the role claims of the validated Authority token, strips any client X-StellaOps-Roles header, and signs them into the envelope. No request body field and no request header supplies a role; the chat query request has no userRoles field, and an X-StellaOps-Roles header is ignored. A caller without verified roles gets no elevated role, so medium- and high-risk actions require approval or are omitted (see Action Types).
Create Conversation
Creates a new conversation session in the caller’s tenant. The request body carries no tenant.
Identity, tenant and scopes come from the gateway-signed identity envelope; raw X-StellaOps-Scopes, X-StellaOps-TenantId and X-Tenant-Id headers are stripped at ingress. Create, add-turn and delete require advisory-ai:operate or advisory-ai:admin; read and list accept advisory-ai:view as well. X-StellaOps-Client names the rate-limit partition.
POST /v1/advisory-ai/conversations
Content-Type: application/json
X-StellaOps-User: user-xyz
X-StellaOps-Client: ui
{
"context": {
"currentCveId": "CVE-2023-44487",
"currentComponent": "pkg:npm/lodash@4.17.21",
"currentImageDigest": "sha256:abc123",
"scanId": "s-789",
"sbomId": "sbom-123"
},
"metadata": {
"source": "ui",
"version": "1.0"
}
}
Response:
{
"conversationId": "conv-abc123",
"tenantId": "tenant-123",
"userId": "user-xyz",
"createdAt": "2026-01-09T12:00:00Z",
"updatedAt": "2026-01-09T12:00:00Z",
"turns": []
}
Send Message (Streaming)
Sends a user message and streams the AI response.
POST /v1/advisory-ai/conversations/{conversationId}/turns
Content-Type: application/json
Accept: text/event-stream
X-StellaOps-User: user-xyz
X-StellaOps-Client: ui
{
"content": "Is CVE-2023-44487 exploitable in our environment?",
"stream": true
}
content is the canonical add-turn payload field. A temporary compatibility shim still accepts legacy message input and maps it to content; empty/whitespace payloads return HTTP 400. Legacy message usage emits a warning header: Warning: 299 - Legacy chat payload field 'message' is deprecated; use 'content'.
Response (Server-Sent Events):
event: token
data: {"content": "Based on the reachability analysis "}
event: token
data: {"content": "[reach:api-gateway:grpc.Server], "}
event: citation
data: {"type": "reach", "path": "api-gateway:grpc.Server", "verified": true}
event: token
data: {"content": "this vulnerability **is reachable** in your environment."}
event: action
data: {"type": "approve", "label": "Accept Risk", "enabled": true, "parameters": {"cve_id": "CVE-2023-44487"}}
event: grounding
data: {"score": 0.92, "citations": 3, "claims": 2}
event: done
data: {"turnId": "turn-xyz", "groundingScore": 0.92}
Conversation add-turn responses now use the same grounded runtime path as the chat gateway. When runtime generation is unavailable, the service returns HTTP 503 with CHAT_RUNTIME_UNAVAILABLE; it does not create an assistant fallback turn, echo the user prompt, or return placeholder answer metadata.
Get Conversation
Retrieves a conversation of the caller’s tenant with its history. Another tenant’s conversation ID returns 404 with no body.
GET /v1/advisory-ai/conversations/{conversationId}
Authorization: Bearer <token>
Response:
{
"conversationId": "conv-abc123",
"tenantId": "tenant-123",
"userId": "user-xyz",
"createdAt": "2026-01-09T12:00:00Z",
"updatedAt": "2026-01-09T12:05:00Z",
"context": { ... },
"turns": [
{
"turnId": "turn-001",
"role": "user",
"content": "Is CVE-2023-44487 exploitable?",
"timestamp": "2026-01-09T12:01:00Z"
},
{
"turnId": "turn-002",
"role": "assistant",
"content": "Based on the reachability analysis...",
"timestamp": "2026-01-09T12:01:05Z",
"evidenceLinks": [
{"type": "reach", "uri": "reach://api-gateway:grpc.Server", "label": "Reachability trace"}
],
"groundingScore": 0.92
}
],
"turnCount": 2
}
List Conversations
Lists the caller’s conversations in the caller’s tenant (filtered by the authenticated subject). The route has no tenant parameter.
GET /v1/advisory-ai/conversations?limit=20
Authorization: Bearer <token>
Delete Conversation
Deletes a conversation of the caller’s tenant and its history. Another tenant’s conversation ID returns 404 with no body and deletes nothing.
DELETE /v1/advisory-ai/conversations/{conversationId}
Authorization: Bearer <token>
Chat Settings
Read or update chat quota/tool settings (defaults come from env).
GET /api/v1/chat/settings
PUT /api/v1/chat/settings?scope=tenant
DELETE /api/v1/chat/settings?scope=tenant
Chat Doctor
Returns quota and tool access status to diagnose limits.
GET /api/v1/chat/doctor
Object Link Format
AI responses include object links that reference internal evidence. These links enable deep navigation to source data.
Supported Link Types
| Type | Format | Example | Description |
|---|---|---|---|
| SBOM | [sbom:{id}] | [sbom:abc123] | Link to SBOM document |
| Reachability | [reach:{service}:{function}] | [reach:api-gateway:grpc.Server] | Link to reachability trace |
| Runtime | [runtime:{service}:traces] | [runtime:api-gateway:traces] | Link to runtime traces |
| VEX | [vex:{issuer}:{digest}] | [vex:stellaops:sha256:abc] | Link to VEX statement |
| Attestation | [attest:dsse:{digest}] | [attest:dsse:sha256:xyz] | Link to DSSE attestation |
| Authority Key | [auth:keys/{keyId}] | [auth:keys/gitlab-oidc] | Link to signing key |
| Documentation | [docs:{path}] | [docs:scopes/ci-webhook] | Link to documentation |
| Finding | [finding:{id}] | [finding:CVE-2024-12345] | Link to finding detail |
| Scan | [scan:{id}] | [scan:scan-2026-02-24-001] | Link to scan detail |
| Policy | [policy:{id}] | [policy:DENY-CRITICAL-PROD] | Link to policy detail |
Link Resolution
Object links are validated by the grounding system:
- Valid links resolve to existing objects and render as clickable chips
- Invalid links are flagged as grounding issues and may lower the grounding score
- Links must be within
MaxLinkDistancecharacters of related claims to count as grounding
Example Response with Links
The vulnerability **CVE-2023-44487** affects your deployment.
**Evidence:**
- The component is present in your SBOM [sbom:scan-2026-01-09-abc123]
- Reachability analysis shows the vulnerable function is called [reach:api-gateway:grpc.Server]
- No VEX statement currently exists for this product
**Recommendation:** Consider creating a VEX statement to document your assessment.
Action Types
The model can propose actions with a response. Before the response is returned, every proposal passes the action policy gate, which checks it against the action registry and the caller’s verified roles (Action role binding). A proposal the caller may not run is omitted from the response; a proposal that needs approval is returned with requiresConfirmation: true. A chat response executes nothing.
Available Actions
| Proposal | Registered action | Required role | Parameters | Risk | Allowed without approval for |
|---|---|---|---|---|---|
| Approve | approve | security-analyst | cve_id, justification, expires_days? | High | admin |
| Quarantine | quarantine | security-lead | image_digest, reason, cve_ids? | Critical | nobody |
| Defer | defer | security-analyst | finding_id, defer_days, reason | Low | every holder of the required role |
admin satisfies every required role, and security-lead satisfies security-analyst. In the registry, a medium-risk action is allowed without approval for security-lead and admin, a high-risk action for admin, and a critical action never: it needs ciso and security-lead approval in production and security-lead approval elsewhere. A proposal of any other type (VEX drafting, pull request, ticket, waive, escalate) does not resolve to a registered action and is omitted. Source: ActionRegistry and ActionPolicyGate in src/AdvisoryAI/StellaOps.AdvisoryAI/Actions/.
Action Button Format
Actions appear in responses using this format:
[Action Label]{action:type,param1=value1,param2=value2}
Example:
You may want to accept this risk: [Accept Risk]{action:approve,cve_id=CVE-2023-44487,rationale=tested}
Action Execution Flow
- Proposal: the model returns proposed actions with its structured response.
- Policy gate: each proposal is checked for a registered type, valid parameters, the required role and the environment, then given its risk-tier decision, using the verified roles only.
- Response: allowed proposals are returned; denied ones are omitted. A returned action carries
actionType,label,policyGateandrequiresConfirmation. - Execution: the chat response executes nothing.
IActionExecutorevaluates the policy and approval again and dispatches only to a registered action connector (architecture, “Action execution boundary”).
Audit Log
Every chat session records an immutable audit trail:
- Prompt hash, redaction metadata, and model identifier
- Tool calls (inputs/outputs hashes) and policy decisions
- Evidence links surfaced in responses
- Action confirmations and results
Audit records live in Postgres with optional DSSE signatures for evidence export. The chat-audit tables (advisoryai.chat_sessions, chat_messages, chat_policy_decisions, chat_tool_invocations, chat_evidence_links) are created by the consolidated startup migration src/AdvisoryAI/__Libraries/StellaOps.AdvisoryAI.Persistence/Migrations/001_advisoryai_consolidated_baseline.sql — no manual psql step. Chat audit is forced onto the same service-owned DSN as the other runtime stores. The pre-1.0 001_chat_audit.sql remains archived under Storage/Migrations/_archived/pre_1.0/mig061/ and is excluded from embedding.
Grounding System
All AI responses are validated for proper grounding—ensuring claims are backed by evidence.
Grounding Score
The grounding score (0.0-1.0) measures response quality:
| Score Range | Quality | Description |
|---|---|---|
| 0.90-1.00 | Excellent | All claims cited, all links valid |
| 0.70-0.89 | Good | Most claims cited, minor issues |
| 0.50-0.69 | Acceptable | Some claims uncited |
| < 0.50 | Rejected | Response regenerated |
Grounding Validation
The validator checks:
- Link Validity: Do object links resolve to real objects?
- Claim Grounding: Are security claims near valid citations?
- Citation Density: Are there enough citations for the response length?
Claim Detection
Claims requiring grounding include:
- Vulnerability status: “is affected”, “is not affected”, “is vulnerable”
- Fix status: “has been fixed”, “is patched”, “is mitigated”
- Severity assessments: “CVSS score is 9.8”, “severity is critical”
- Investigation status: “is under investigation”
Grounding Issues
| Issue Type | Severity | Description |
|---|---|---|
InvalidLink | Error | Object link does not resolve |
UngroundedClaim | Warning | Security claim without nearby citation |
BelowThreshold | Critical | Overall score below minimum |
Configuration
AdvisoryAI:
Chat:
Grounding:
MinGroundingScore: 0.5 # Minimum acceptable score
MaxLinkDistance: 200 # Max chars between claim and citation
Example Flows
Vulnerability Investigation
User: What is the status of CVE-2023-44487 in our environment?
Assistant: CVE-2023-44487 (HTTP/2 Rapid Reset) **is reachable** in your environment
[reach:api-gateway:grpc.Server].
**Status:** AFFECTED
- Component: grpc-go v1.56.0 [sbom:scan-abc123]
- Reachable path: api-gateway -> grpc.Server.handleStream()
- No VEX statement exists
**Recommended actions:**
[Accept Risk]{action:approve,cve_id=CVE-2023-44487}
[Create VEX]{action:create_vex,product=api-gateway,vulnerability=CVE-2023-44487,status=affected}
Action Execution
User: Please create a VEX statement marking CVE-2023-44487 as affected for api-gateway.
Assistant: I will create a VEX statement with the following details:
- **Product:** api-gateway
- **Vulnerability:** CVE-2023-44487
- **Status:** affected
- **Justification:** Vulnerable code is reachable
[Create VEX Statement]{action:create_vex,product=api-gateway,vulnerability=CVE-2023-44487,status=affected,justification=vulnerable_code_present}
Configuration
ConversationRetention is enforced for the durable conversation store: the web host’s ConversationRetentionService calls advisoryai.prune_idle_conversations every hour and removes each conversation idle longer than the window, together with its turns. Adding a turn refreshes a conversation’s idle clock. doctor.advisoryai.retention.conversation-state reports conversations idle past the window and live rows above ConversationBudget:MaximumConversationRows. The chat audit lineage (chat_* tables) keeps its own 90-day window and is not affected.
AdvisoryAI:
Guardrails:
EntropyThreshold: 3.5
EntropyMinLength: 20
AllowlistFile: "data/advisory-ai/allowlist.txt"
Chat:
ConversationRetention: '7.00:00:00' # 7 days; idle conversations and their turns are pruned hourly
ConversationBudget:
MaximumConversationRows: 1000000 # Doctor budget for live conversation rows
MaxTurnsPerConversation: 50
TokenBudget: 8192
StreamingEnabled: true
Quotas:
RequestsPerMinute: 60
RequestsPerDay: 500
TokensPerDay: 100000
ToolCallsPerDay: 10000
Tools:
AllowAll: false
AllowedTools:
- "vex.query"
- "sbom.read"
- "scanner.findings.topk"
Audit:
Enabled: true
ConnectionString: "Host=localhost;Database=stellaops;Username=stellaops;Password=changeme"
SchemaName: "advisoryai"
IncludeEvidenceBundle: false
RetentionPeriod: '90.00:00:00'
Grounding:
MinGroundingScore: 0.5
MaxLinkDistance: 200
Actions:
RequireConfirmation: true
RequirePolicyAllow: true
AuditAllExecutions: true
Error Handling
| Status Code | Error | Description |
|---|---|---|
| 400 | InvalidRequest | Malformed request body |
| 401 | Unauthorized | Missing or invalid token |
| 403 | Forbidden | Insufficient permissions |
| 404 | ConversationNotFound | Conversation does not exist |
| 429 | RateLimited | Too many requests |
| 500 | InternalError | Server error |
See Also
- AdvisoryAI Architecture
- Deployment Guide
- Security Guardrails
- Controlled Conversational Interface Advisory
