Action approval uses only the verified identity’s roles (breaking for a caller that sent roles).POST /api/v1/chat/query copied userRoles from the request body, and the legacy conversation turn read an X-StellaOps-Roles header; the action policy gate allows a high-risk action without approval for admin and a medium-risk one for security-lead or admin, so any authenticated caller could skip the approval step by claiming a role. The roles now come only from the gateway-signed identity envelope (Security/VerifiedActionRoles). AdvisoryChatQueryRequest.userRoles is removed (a value sent anyway is ignored), and the header is no longer read; the gateway also strips a client X-StellaOps-Roles header. A caller without verified roles gets approval-required or omitted actions. Sprint SPRINT_20261006_114 (PCD-904); OpenAPI changelog docs/changelog/openapi/2026-10-20-advisory-ai-chat-query-roles-removed.md.
The OpenAPI document is the host’s served document, and a test keeps it so.openapi/v1.json and api-reference.md were a 2026-05-20 capture (85 operations); they now list all 181 operations the host serves, including the /api/advisoryai/v1/** consolidated aliases, OpsMemory, the doctor-check, plugin-probe and search-adapter status routes, and the LLM adapter routes the host maps when AdvisoryAI:Adapters:Llm:Enabled is true. AdvisoryAiOpenApiCaptureTests captures the document and fails while the committed one differs from it. No route changed. The internal IAuthorizationService lost GetTenantId and GetUserId, which had no caller and fell back to default and anonymous; no behavior changes. Sprint SPRINT_20261006_114 (PCD-892); OpenAPI changelog docs/changelog/openapi/2026-10-20-advisory-ai-full-host-recapture.md.
One unified index run per host, and the refresh interval is a gap after completion. The startup rebuild, the periodic refresh and POST /v1/search/index/rebuild share one gate. A rebuild requested while another run is in flight now answers 409 Conflict with error and running (kind, startedAt) and starts no upstream read; before, it paged Findings a second time beside the running pass. The next periodic run starts no earlier than UnifiedIndexRefreshIntervalSeconds after the previous run of any kind finished; before, a run longer than the interval was followed at once by the next one. Every run still reads all Findings pages: there is no skip-when-unchanged step. Sprint SPRINT_20261006_114 (PCD-962); OpenAPI changelog docs/changelog/openapi/2026-10-20-advisory-ai-unified-rebuild-in-progress.md.
Conversations, chat, consent and runs are bound to the authenticated tenant (breaking for a caller that named a tenant). The legacy /v1/advisory-ai/conversations* routes, the /api/v1/chat and /v1/advisory-ai/runs groups and the consent routes carry .RequireTenant() and read the tenant from the stellaops:tenant claim. CreateConversationRequest.tenantId and the list’s tenantId query parameter are removed (a value sent anyway is ignored). Conversation reads, turns and deletes match tenant and ID: another tenant’s conversation ID returns 404 with no body, as an unknown ID does. A principal without a tenant gets 400 / tenant_missing instead of writing under default. Sprint SPRINT_20261018_009 (PCD-866); OpenAPI changelog docs/changelog/openapi/2026-10-18-advisory-ai-conversation-tenant-binding.md.
OpsMemory tenancy is claim-bound (breaking for any caller that relied on naming its own tenant). Every /api/v1/opsmemory/* endpoint now isolates on the authenticated stellaops:tenant claim (OpsMemoryTenantResolver); the route group carries .RequireTenant(). The tenantId query parameter and RecordDecisionRequest.TenantId body field became optional conflict checks: a value that disagrees with the claim is rejected with 400 / error_code: tenant_conflict, and a request carrying no tenant claim is rejected with 400 / error_code: tenant_missing. Previously the caller-supplied tenantId was passed to PostgresOpsMemoryStore as the sole isolation key. Sprint SPRINT_20260712_001 (TEN-1); contract in architecture.md §15.