151 lines
4.2 KiB
JavaScript
151 lines
4.2 KiB
JavaScript
/**
|
||
* 全链路审计追溯系统 · Audit Trail
|
||
* 语言膜核心组件
|
||
*
|
||
* 每一条通过语言膜的请求都会被记录:
|
||
* WHO — 谁发起的(HLDP编号 / IP / 会话ID)
|
||
* WHAT — 什么操作(HLDP格式化的指令内容)
|
||
* WHEN — 什么时间(ISO时间戳)
|
||
* WHERE — 在哪个频道/模块(频道编号/模块编号)
|
||
* HOW — 执行结果(成功/失败/异常)
|
||
* WHY — 什么触发的(上下文链路)
|
||
*
|
||
* 编号: SY-MEMBRANE-AUDIT-001
|
||
* 守护: 铸渊 · ICE-GL-ZY001
|
||
* 版权: 国作登字-2026-A-00037559
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const crypto = require('crypto');
|
||
|
||
// 审计日志存储目录
|
||
const AUDIT_DIR = process.env.ZY_AUDIT_DIR
|
||
|| path.join(process.env.ZY_ROOT || process.cwd(), 'data', 'audit-trail');
|
||
|
||
/**
|
||
* 生成审计事件ID
|
||
* 格式: AT-YYYYMMDD-HHmmss-XXXX
|
||
*/
|
||
function generateAuditId() {
|
||
const now = new Date();
|
||
const date = now.toISOString().replace(/[-:T]/g, '').slice(0, 14);
|
||
const rand = crypto.randomBytes(2).toString('hex').toUpperCase();
|
||
return `AT-${date}-${rand}`;
|
||
}
|
||
|
||
/**
|
||
* 创建审计条目
|
||
*
|
||
* @param {object} params
|
||
* @param {string} params.who — 请求发起者标识
|
||
* @param {string} params.what — 操作描述
|
||
* @param {string} [params.where] — 频道/模块标识
|
||
* @param {string} [params.why] — 触发原因/上下文
|
||
* @param {string} [params.sourceIp] — 来源IP
|
||
* @param {string} [params.sessionId] — 会话ID
|
||
* @returns {object} 审计条目
|
||
*/
|
||
function createEntry(params) {
|
||
return {
|
||
audit_id: generateAuditId(),
|
||
timestamp: new Date().toISOString(),
|
||
who: params.who || 'unknown',
|
||
what: params.what || '',
|
||
when: new Date().toISOString(),
|
||
where: params.where || 'membrane-gateway',
|
||
why: params.why || '',
|
||
how: 'pending',
|
||
source_ip: params.sourceIp || '',
|
||
session_id: params.sessionId || '',
|
||
responsibility: 'undetermined',
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 完成审计条目(填入执行结果)
|
||
*
|
||
* @param {object} entry — createEntry 返回的条目
|
||
* @param {string} result — 'success' | 'failure' | 'error' | 'denied'
|
||
* @param {string} [responsibility] — 'system' | 'human' | 'persona'
|
||
* @param {string} [detail] — 附加信息
|
||
* @returns {object} 完成后的审计条目
|
||
*/
|
||
function completeEntry(entry, result, responsibility, detail) {
|
||
entry.how = result;
|
||
entry.responsibility = responsibility || 'system';
|
||
entry.completed_at = new Date().toISOString();
|
||
if (detail) {
|
||
entry.detail = detail;
|
||
}
|
||
return entry;
|
||
}
|
||
|
||
/**
|
||
* 持久化审计条目到磁盘
|
||
* 按日期分文件存储: data/audit-trail/YYYY-MM-DD.jsonl
|
||
*
|
||
* @param {object} entry — 审计条目
|
||
*/
|
||
function persist(entry) {
|
||
try {
|
||
if (!fs.existsSync(AUDIT_DIR)) {
|
||
fs.mkdirSync(AUDIT_DIR, { recursive: true });
|
||
}
|
||
const dateStr = new Date().toISOString().slice(0, 10);
|
||
const filePath = path.join(AUDIT_DIR, `${dateStr}.jsonl`);
|
||
fs.appendFileSync(filePath, JSON.stringify(entry) + '\n', 'utf8');
|
||
} catch (err) {
|
||
// 审计系统自身不能阻塞请求流,记录到 stderr
|
||
process.stderr.write(`[AUDIT] persist error: ${err.message}\n`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 查询审计日志
|
||
*
|
||
* @param {object} [filter]
|
||
* @param {string} [filter.date] — 'YYYY-MM-DD'
|
||
* @param {string} [filter.who] — 按 who 过滤
|
||
* @param {number} [filter.limit] — 最大返回条数
|
||
* @returns {Array} 审计条目列表
|
||
*/
|
||
function query(filter) {
|
||
const opts = filter || {};
|
||
const dateStr = opts.date || new Date().toISOString().slice(0, 10);
|
||
const filePath = path.join(AUDIT_DIR, `${dateStr}.jsonl`);
|
||
|
||
if (!fs.existsSync(filePath)) {
|
||
return [];
|
||
}
|
||
|
||
const lines = fs.readFileSync(filePath, 'utf8')
|
||
.split('\n')
|
||
.filter(Boolean);
|
||
|
||
let entries = lines.map(line => {
|
||
try { return JSON.parse(line); } catch (_) { return null; }
|
||
}).filter(Boolean);
|
||
|
||
if (opts.who) {
|
||
entries = entries.filter(e => e.who === opts.who);
|
||
}
|
||
|
||
if (opts.limit && opts.limit > 0) {
|
||
entries = entries.slice(-opts.limit);
|
||
}
|
||
|
||
return entries;
|
||
}
|
||
|
||
module.exports = {
|
||
createEntry,
|
||
completeEntry,
|
||
persist,
|
||
query,
|
||
generateAuditId,
|
||
AUDIT_DIR,
|
||
};
|