chore: 合入 stash Ops hardening 与 migration 000041

Ask/catalog 权限与审计加固、量表读权限统一,以及未提交的 ops hardening 变更。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-13 01:46:34 +08:00
co-authored by Cursor
parent 4889ff5916
commit 62cd8c45dd
70 changed files with 848 additions and 69 deletions
+12 -2
View File
@@ -272,8 +272,18 @@ export const adminApi = {
message_count: number
created_at: string
updated_at: string
messages: Array<{ id: string; role: string; content: string; created_at: string }>
}>('GET', `/ask/threads/${id}`),
askThreadMessages: (id: string) =>
request<{
id: string
user_id: string
profile_id: string
scene?: string
message_count: number
created_at: string
updated_at: string
messages: Array<{ id: string; role: string; content: string; created_at: string }>
}>('GET', `/ask/threads/${id}/messages`),
filterRules: () =>
request<{
items: Array<{
@@ -482,7 +492,7 @@ export const adminApi = {
request<Record<string, unknown>>('GET', `/ai/tools/${id}`),
blockPolicies: () =>
request<{ items: Array<Record<string, unknown>> }>('GET', '/content-safety/block-policies'),
blockPolicie: (id: string) =>
blockPolicy: (id: string) =>
request<Record<string, unknown>>('GET', `/content-safety/block-policies/${id}`),
cases: () =>
request<{ items: Array<Record<string, unknown>> }>('GET', '/content-safety/cases'),
+17 -4
View File
@@ -5,7 +5,9 @@ import { adminApi } from '@/api/client'
import { useAuthStore } from '@/stores/auth'
type Thread = Awaited<ReturnType<typeof adminApi.askThreads>>['items'][number]
type Detail = Awaited<ReturnType<typeof adminApi.askThread>>
type Detail = Awaited<ReturnType<typeof adminApi.askThread>> & {
messages?: Array<{ id: string; role: string; content: string; created_at: string }>
}
type Feedback = Awaited<ReturnType<typeof adminApi.askFeedback>>['items'][number]
const auth = useAuthStore()
@@ -20,8 +22,10 @@ const rating = ref(4)
const tag = ref('helpful')
const note = ref('')
const fbMsg = ref('')
const transcriptDenied = ref(false)
const canWriteFeedback = () => auth.can('admin.ask.feedback.write')
const canReadTranscript = () => auth.can('admin.ask.transcript.read')
async function load() {
loading.value = true
@@ -42,8 +46,16 @@ async function openThread(id: string) {
detailErr.value = ''
selected.value = null
fbMsg.value = ''
transcriptDenied.value = false
try {
selected.value = await adminApi.askThread(id)
const meta = await adminApi.askThread(id)
selected.value = { ...meta, messages: [] }
if (canReadTranscript()) {
const full = await adminApi.askThreadMessages(id)
selected.value = full
} else {
transcriptDenied.value = true
}
} catch (e) {
detailErr.value = e instanceof Error ? e.message : '详情失败'
} finally {
@@ -123,8 +135,9 @@ onMounted(load)
{{ selected.scene || '—' }} · {{ selected.message_count }} ·
<RouterLink :to="`/users/${selected.user_id}`">用户</RouterLink>
</p>
<ul class="msgs">
<li v-for="m in selected.messages" :key="m.id" :class="m.role">
<p v-if="transcriptDenied" class="muted">无对话原文权限admin.ask.transcript.read</p>
<ul v-else class="msgs">
<li v-for="m in selected.messages || []" :key="m.id" :class="m.role">
<span class="role">{{ m.role }}</span>
<p>{{ m.content }}</p>
<time>{{ fmtTime(m.created_at) }}</time>
+10 -10
View File
@@ -5,15 +5,15 @@ import { adminApi } from '@/api/client'
type Row = { id: string; code?: string; slug?: string; title?: string; status?: string }
const catalogs = [
{ key: 'publications', label: '定时发布', load: () => adminApi.publications() },
{ key: 'knowledgeChunks', label: '知识块', load: () => adminApi.knowledgeChunks() },
{ key: 'tools', label: '工具定义', load: () => adminApi.tools() },
{ key: 'blockPolicies', label: '拦截策略', load: () => adminApi.blockPolicies() },
{ key: 'cases', label: '审核', load: () => adminApi.cases() },
{ key: 'events', label: '危机事件', load: () => adminApi.events() },
{ key: 'interventions', label: '干预结果', load: () => adminApi.interventions() },
{ key: 'handoffs', label: '转接', load: () => adminApi.handoffs() },
{ key: 'privacy', label: '隐私请求', load: () => adminApi.requests() },
{ key: 'publications', label: '定时发布(目录)', load: () => adminApi.publications() },
{ key: 'knowledgeChunks', label: '知识块(目录)', load: () => adminApi.knowledgeChunks() },
{ key: 'tools', label: '工具定义(目录)', load: () => adminApi.tools() },
{ key: 'blockPolicies', label: '拦截策略(目录)', load: () => adminApi.blockPolicies() },
{ key: 'cases', label: '审核类型(目录)', load: () => adminApi.cases() },
{ key: 'events', label: '危机类型(目录)', load: () => adminApi.events() },
{ key: 'interventions', label: '干预类型(目录)', load: () => adminApi.interventions() },
{ key: 'handoffs', label: '转接类型(目录)', load: () => adminApi.handoffs() },
{ key: 'privacy', label: '隐私类型(目录)', load: () => adminApi.requests() },
{ key: 'star', label: '星座配置', load: () => adminApi.starConfigs() },
{ key: 'rhythm', label: '节律配置', load: () => adminApi.rhythmConfigs() },
{ key: 'decks', label: '意象牌组', load: () => adminApi.imageCardDecks() },
@@ -50,7 +50,7 @@ onMounted(() => {
<template>
<section>
<h1>目录只读仓</h1>
<p class="muted">ECR-026040 运营只读目录聚合不可写发布</p>
<p class="muted">ECR-026040 运营只读目录聚合类型占位非用户个案 docs/WAVE0/CATALOG_VS_CASE.md</p>
<div class="tabs">
<button
v-for="(c, i) in catalogs"