"""审计日志 — append-only 通用审计原语。 审计粒度(什么该记、记到什么程度)在 skill 里说明;本模块只提供 record_audit 通用原语,所有概念能力(task/question/deliverable)的状态迁移/流转都调它记一条。 设计约束: - append-only:只 INSERT,不 UPDATE/DELETE(审计独立性,防自删)。 - 租户隔离:每条必带 tenant_id。 - sor 参数可选:在已有 sqlorContext 里调用时传入避免嵌套开连接。 """ import logging from datetime import datetime from sqlor.dbpools import DBPools from appPublic.uniqueID import getID DBNAME = "pipeline" logger = logging.getLogger("pipeline.audit") def _get_db(): db = DBPools() if not db.databases: from appPublic.jsonConfig import getConfig config = getConfig() if config.databases: db.databases = config.databases return db, DBNAME async def record_audit(tenant_id, entity, entity_id, action, from_state=None, to_state=None, who=None, agent_id=None, detail=None, sor=None): """追加一条审计记录(只追加不改删)。 Args: tenant_id: 租户ID(必带,隔离) entity: 实体类型(如 pipeline_tasks / pipeline_agent_questions / pipeline_deliverables) entity_id: 实体ID action: 动作(如 claim/submit/approve/reject/raise/escalate/resolve/set_state) from_state / to_state: 状态迁移前后(可空) who: 操作角色(agent.{role} 或 {orgtype}.{role}) agent_id: 具体 agent 标识 detail: 附加详情 sor: 可选的 sqlor context,传入则复用(避免嵌套开连接) """ data = { 'id': getID(), 'tenant_id': tenant_id or '', 'entity': entity or '', 'entity_id': entity_id or '', 'action': action or '', 'from_state': from_state, 'to_state': to_state, 'who': who, 'agent_id': agent_id, 'detail': detail, # 微秒精度,保证 append-only 轨迹可精确排序(秒级同秒内会乱序) 'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'), } if sor is not None: await sor.C('audit_log', data) return True db, dbname = _get_db() async with db.sqlorContext(dbname) as s: await s.C('audit_log', data) return True