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:

Controlled Gateway and Budgets

Sanctioned Tools (v1)


API Reference

Endpoint Families and Migration Timeline

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

AI responses include object links that reference internal evidence. These links enable deep navigation to source data.

TypeFormatExampleDescription
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

Object links are validated by the grounding system:

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

ProposalRegistered actionRequired roleParametersRiskAllowed without approval for
Approveapprovesecurity-analystcve_id, justification, expires_days?Highadmin
Quarantinequarantinesecurity-leadimage_digest, reason, cve_ids?Criticalnobody
Deferdefersecurity-analystfinding_id, defer_days, reasonLowevery 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

  1. Proposal: the model returns proposed actions with its structured response.
  2. 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.
  3. Response: allowed proposals are returned; denied ones are omitted. A returned action carries actionType, label, policyGate and requiresConfirmation.
  4. Execution: the chat response executes nothing. IActionExecutor evaluates 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:

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 RangeQualityDescription
0.90-1.00ExcellentAll claims cited, all links valid
0.70-0.89GoodMost claims cited, minor issues
0.50-0.69AcceptableSome claims uncited
< 0.50RejectedResponse regenerated

Grounding Validation

The validator checks:

  1. Link Validity: Do object links resolve to real objects?
  2. Claim Grounding: Are security claims near valid citations?
  3. Citation Density: Are there enough citations for the response length?

Claim Detection

Claims requiring grounding include:

Grounding Issues

Issue TypeSeverityDescription
InvalidLinkErrorObject link does not resolve
UngroundedClaimWarningSecurity claim without nearby citation
BelowThresholdCriticalOverall 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 CodeErrorDescription
400InvalidRequestMalformed request body
401UnauthorizedMissing or invalid token
403ForbiddenInsufficient permissions
404ConversationNotFoundConversation does not exist
429RateLimitedToo many requests
500InternalErrorServer error

See Also