arela
Version:
AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.
180 lines (179 loc) • 5.51 kB
JavaScript
import path from "node:path";
import Database from "better-sqlite3";
import fs from "fs-extra";
export class AuditMemory {
cwd;
constructor(cwd = process.cwd()) {
this.cwd = cwd;
}
get dbPath() {
return path.join(this.cwd, ".arela", "memory", "audit.db");
}
async isReady() {
return fs.pathExists(this.dbPath);
}
async init() {
const db = this.openDb();
db.close();
}
async logDecision(entry) {
const db = this.openDb();
try {
const stmt = db.prepare(`
INSERT INTO audit_log (
timestamp,
agent,
action,
input_hash,
output_hash,
result,
metadata,
commit_hash,
ticket_id,
policy_violations
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
stmt.run(entry.timestamp ?? new Date().toISOString(), entry.agent, entry.action, entry.inputHash ?? null, entry.outputHash ?? null, entry.result, entry.metadata ? JSON.stringify(entry.metadata) : null, entry.commitHash ?? null, entry.ticketId ?? null, entry.policyViolations ? JSON.stringify(entry.policyViolations) : null);
}
finally {
db.close();
}
}
async getAuditTrail(filter) {
if (!(await this.isReady())) {
return { scope: "all", entries: [] };
}
const db = this.openDb();
try {
const where = [];
const params = [];
if (filter?.commitHash) {
where.push("commit_hash = ?");
params.push(filter.commitHash);
}
if (filter?.ticketId) {
where.push("ticket_id = ?");
params.push(filter.ticketId);
}
const limit = Math.max(1, filter?.limit ?? 25);
const query = `
SELECT *
FROM audit_log
${where.length > 0 ? `WHERE ${where.join(" AND ")}` : ""}
ORDER BY timestamp DESC
LIMIT ?
`;
const rows = db.prepare(query).all(...params, limit);
return {
scope: filter?.commitHash ? "commit" : filter?.ticketId ? "ticket" : "all",
filter: filter?.commitHash ?? filter?.ticketId,
entries: rows.map(deserializeAuditRow),
};
}
finally {
db.close();
}
}
async getStats() {
if (!(await this.isReady())) {
return {
ready: false,
entries: 0,
success: 0,
failure: 0,
pending: 0,
agents: {},
dbPath: this.dbPath,
};
}
const db = this.openDb();
try {
const totals = db
.prepare(`
SELECT
COUNT(*) as entries,
SUM(CASE WHEN result = 'success' THEN 1 ELSE 0 END) as success,
SUM(CASE WHEN result = 'failure' THEN 1 ELSE 0 END) as failure,
SUM(CASE WHEN result = 'pending' THEN 1 ELSE 0 END) as pending
FROM audit_log
`)
.get();
const agentRows = db
.prepare(`SELECT agent, COUNT(*) as count FROM audit_log GROUP BY agent ORDER BY count DESC`)
.all();
const agents = {};
for (const row of agentRows) {
if (row.agent) {
agents[row.agent] = row.count;
}
}
return {
ready: true,
entries: totals?.entries ?? 0,
success: totals?.success ?? 0,
failure: totals?.failure ?? 0,
pending: totals?.pending ?? 0,
agents,
dbPath: this.dbPath,
};
}
finally {
db.close();
}
}
openDb() {
fs.ensureDirSync(path.dirname(this.dbPath));
const db = new Database(this.dbPath);
db.pragma("journal_mode = WAL");
db.pragma("foreign_keys = ON");
this.initSchema(db);
return db;
}
initSchema(db) {
db.exec(`
CREATE TABLE IF NOT EXISTS audit_log (
id INTEGER PRIMARY KEY,
timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
agent TEXT NOT NULL,
action TEXT NOT NULL,
input_hash TEXT,
output_hash TEXT,
result TEXT NOT NULL,
metadata TEXT,
commit_hash TEXT,
ticket_id TEXT,
policy_violations TEXT
);
CREATE INDEX IF NOT EXISTS idx_audit_commit ON audit_log(commit_hash);
CREATE INDEX IF NOT EXISTS idx_audit_ticket ON audit_log(ticket_id);
CREATE INDEX IF NOT EXISTS idx_audit_agent ON audit_log(agent);
`);
}
}
function deserializeAuditRow(row) {
return {
...row,
timestamp: row.timestamp,
metadata: parseJson(row.metadata),
policyViolations: parseJson(row.policyViolations) ?? undefined,
result: normalizeResult(row.result),
};
}
function parseJson(value) {
if (!value || typeof value !== "string") {
return undefined;
}
try {
return JSON.parse(value);
}
catch {
return undefined;
}
}
function normalizeResult(value) {
if (value === "success" || value === "failure" || value === "pending") {
return value;
}
return "pending";
}
//# sourceMappingURL=audit.js.map