Files
Agent-System-Frontend/src/rdmAuditPresentation.ts
T
weichen 814643f277 Present RDM audit records in business language
Why: Project members need to understand who changed what, why, and with what impact without reading operation codes or raw audit payloads.

Evidence: A RED presentation test first failed on the missing humanization layer; 276 frontend tests passed, design guard 10 checks passed, knip stayed at baseline 43, and npm run build passed.

Impact: Humanize tree, relation, state, and operation vocabulary; render structured reason, impact, source, and outcome fields; raw event detail is no longer shown in the audit timeline.

Refs: TT-U01, TT-U02, TT-U03, TT-U04; two trees one graph stage-five plan
2026-10-14 17:04:06 +00:00

135 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export interface AuditEventViewInput {
seq: number
at: string
op: string
actor: string
source: string
target: { kind: string; id: string; title?: string }
detail?: string
operationId: string
disclosure: string
summary?: string
result?: string
reason?: string
impact?: string
}
interface AuditEventPresentation {
action: string
subject: string
object: string
outcome: string
reason: string
impact: string
source: string
}
const KIND_LABELS: Record<string, string> = {
doc: '文档', section: '章节', node: '节点', note: '知识', slice: '资料切片',
requirement: '要求', risk: '风险', proposal: '变更提案', relation: '追溯关系',
template: '模板', project: '项目', conclusion: '结论', issue: '问题',
}
const STATE_LABELS: Record<string, string> = {
active: '已生效', candidate: '待确认', pending: '待处理', baselined: '已建立基线',
approved: '已批准', rejected: '已拒绝', revoked: '已撤销', superseded: '已被取代',
failed: '失败', committed: '已生效', accepted: '已接受', cancelled: '已取消',
unknown: '结果未知', restricted: '部分信息受限',
}
const OP_LABELS: Record<string, string> = {
'doc.instantiate': '创建文档实例',
'doc.section.add': '新增文档章节',
'doc.section.update': '更新文档章节',
'doc.node.add': '新增文档节点',
'doc.node.update': '更新文档节点',
'doc.approve': '批准文档版本',
'note.add': '新增项目知识',
'note.update': '更新项目知识',
'relation.add': '建立追溯关系',
'relation.invalidate': '标记关系待复核',
'proposal.create': '提出结构变更',
'proposal.apply': '应用结构变更',
'proposal.reject': '拒绝结构变更',
'risk.create': '发现项目风险',
'risk.answer': '回答风险澄清',
'risk.resolve': '完成风险处理',
'req.add': '新增项目要求',
'req.update': '更新项目要求',
}
const RELATION_LABELS: Record<string, string> = {
verifies: '验证', supports: '支持', derives: '派生自', impacts: '影响',
traces: '追溯到', replaces: '取代', conflicts: '存在冲突', references: '引用',
}
const LAYER_LABELS: Record<string, string> = {
compliance: '合规关系', knowledge: '知识关系', document: '文档关系', project: '项目关系',
}
export function humanizeKind(kind: string): string {
return KIND_LABELS[kind] || '项目对象'
}
export function humanizeState(state: string): string {
return STATE_LABELS[state] || '状态已记录'
}
function humanizeOperation(op: string): string {
if (OP_LABELS[op]) return OP_LABELS[op]
if (op.startsWith('doc.')) return '更新文档内容'
if (op.startsWith('note.') || op.startsWith('knowledge.')) return '更新项目知识'
if (op.startsWith('relation.') || op.startsWith('trace.')) return '更新追溯关系'
if (op.startsWith('risk.')) return '处理项目风险'
if (op.startsWith('proposal.')) return '处理结构变更'
return '更新项目内容'
}
function humanizeActor(actor: string): string {
if (!actor) return '未知成员'
if (/^[a-z][a-z0-9._-]*$/i.test(actor) && !actor.includes('-') && !actor.includes('_')) {
return actor.charAt(0).toUpperCase() + actor.slice(1)
}
return actor
}
function humanizeSource(source: string): string {
if (!source) return '当时未记录来源'
const template = /^template:([^@]+)@(.+)$/.exec(source)
if (template) {
const title = template[1] === 'dhf-proposal' ? 'DHF 项目建议书' : template[1]
return `模板:${title}(版本 ${template[2]})`
}
if (source === 'host') return '系统受信操作记录'
if (source.startsWith('message:')) return '项目对话'
if (source.startsWith('artifact:')) return '项目文件'
return '已登记项目来源'
}
function humanizeOutcome(input: AuditEventViewInput): string {
if (input.disclosure !== 'ok') return input.disclosure === 'model-candidate' ? '模型候选,尚未确认' : '部分信息受限'
return humanizeState(input.result || 'committed')
}
export function describeAuditEvent(input: AuditEventViewInput): AuditEventPresentation {
const targetTitle = input.target?.title ? `“${input.target.title}”` : ''
return {
action: input.summary || humanizeOperation(input.op),
subject: humanizeActor(input.actor),
object: `${humanizeKind(input.target?.kind)}${targetTitle}`,
outcome: humanizeOutcome(input),
reason: input.reason || '未记录具体原因',
impact: input.impact || '影响范围尚未记录',
source: humanizeSource(input.source),
}
}
export function humanizeRelation(input: { layer?: string; type?: string; kind?: string; state?: string }) {
const type = input.type || input.kind || ''
return {
layer: LAYER_LABELS[input.layer || ''] || '项目关系',
type: RELATION_LABELS[type] || '关联',
state: humanizeState(input.state || ''),
}
}