tractatus/src/models/GovernanceLog.model.js
TheFlow 09f706c51b feat: fix documentation system - cards, PDFs, TOC, and navigation
- Fixed download icon size (1.25rem instead of huge black icons)
- Uploaded all 12 PDFs to production server
- Restored table of contents rendering for all documents
- Fixed modal cards with proper CSS and event handlers
- Replaced all docs-viewer.html links with docs.html
- Added nginx redirect from /docs/* to /docs.html
- Fixed duplicate headers in modal sections
- Improved cache-busting with timestamp versioning

All documentation features now working correctly:
 Card-based document viewer with modals
 PDF downloads with proper icons
 Table of contents navigation
 Consistent URL structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-07 22:51:55 +13:00

216 lines
5.4 KiB
JavaScript

/**
* GovernanceLog Model
* Audit trail for Tractatus governance actions
*/
const { ObjectId } = require('mongodb');
const { getCollection } = require('../utils/db.util');
class GovernanceLog {
/**
* Create governance log entry
*/
static async create(data) {
const collection = await getCollection('governance_logs');
const log = {
action: data.action, // BLOG_TOPIC_SUGGESTION, MEDIA_TRIAGE, etc.
user_id: data.user_id ? new ObjectId(data.user_id) : null,
user_email: data.user_email,
timestamp: data.timestamp || new Date(),
// Tractatus governance data
quadrant: data.quadrant || null, // STR/OPS/TAC/SYS/STO
boundary_check: data.boundary_check || null, // BoundaryEnforcer result
cross_reference: data.cross_reference || null, // CrossReferenceValidator result
pressure_level: data.pressure_level || null, // ContextPressureMonitor result
outcome: data.outcome, // ALLOWED/BLOCKED/QUEUED_FOR_APPROVAL
// Action details
details: data.details || {},
// If action was blocked
blocked_reason: data.blocked_reason || null,
blocked_section: data.blocked_section || null,
// Metadata
service: data.service || 'unknown',
environment: process.env.NODE_ENV || 'production',
ip_address: data.ip_address || null,
created_at: new Date()
};
const result = await collection.insertOne(log);
return { ...log, _id: result.insertedId };
}
/**
* Find logs by action type
*/
static async findByAction(action, options = {}) {
const collection = await getCollection('governance_logs');
const { limit = 50, skip = 0, startDate, endDate } = options;
const filter = { action };
if (startDate || endDate) {
filter.timestamp = {};
if (startDate) filter.timestamp.$gte = new Date(startDate);
if (endDate) filter.timestamp.$lte = new Date(endDate);
}
return await collection
.find(filter)
.sort({ timestamp: -1 })
.skip(skip)
.limit(limit)
.toArray();
}
/**
* Find logs by user
*/
static async findByUser(userId, options = {}) {
const collection = await getCollection('governance_logs');
const { limit = 50, skip = 0 } = options;
return await collection
.find({ user_id: new ObjectId(userId) })
.sort({ timestamp: -1 })
.skip(skip)
.limit(limit)
.toArray();
}
/**
* Find blocked actions
*/
static async findBlocked(options = {}) {
const collection = await getCollection('governance_logs');
const { limit = 50, skip = 0 } = options;
return await collection
.find({ outcome: 'BLOCKED' })
.sort({ timestamp: -1 })
.skip(skip)
.limit(limit)
.toArray();
}
/**
* Find by outcome
*/
static async findByOutcome(outcome, options = {}) {
const collection = await getCollection('governance_logs');
const { limit = 50, skip = 0 } = options;
return await collection
.find({ outcome })
.sort({ timestamp: -1 })
.skip(skip)
.limit(limit)
.toArray();
}
/**
* Find by quadrant
*/
static async findByQuadrant(quadrant, options = {}) {
const collection = await getCollection('governance_logs');
const { limit = 50, skip = 0 } = options;
return await collection
.find({ quadrant })
.sort({ timestamp: -1 })
.skip(skip)
.limit(limit)
.toArray();
}
/**
* Get statistics
*/
static async getStatistics(startDate, endDate) {
const collection = await getCollection('governance_logs');
const filter = {};
if (startDate || endDate) {
filter.timestamp = {};
if (startDate) filter.timestamp.$gte = new Date(startDate);
if (endDate) filter.timestamp.$lte = new Date(endDate);
}
const [totalLogs] = await collection.aggregate([
{ $match: filter },
{
$group: {
_id: null,
total: { $sum: 1 },
allowed: {
$sum: { $cond: [{ $eq: ['$outcome', 'ALLOWED'] }, 1, 0] }
},
blocked: {
$sum: { $cond: [{ $eq: ['$outcome', 'BLOCKED'] }, 1, 0] }
},
queued: {
$sum: { $cond: [{ $eq: ['$outcome', 'QUEUED_FOR_APPROVAL'] }, 1, 0] }
}
}
}
]).toArray();
const byQuadrant = await collection.aggregate([
{ $match: { ...filter, quadrant: { $ne: null } } },
{
$group: {
_id: '$quadrant',
count: { $sum: 1 }
}
}
]).toArray();
const byAction = await collection.aggregate([
{ $match: filter },
{
$group: {
_id: '$action',
count: { $sum: 1 }
}
},
{ $sort: { count: -1 } },
{ $limit: 10 }
]).toArray();
return {
summary: totalLogs[0] || {
total: 0,
allowed: 0,
blocked: 0,
queued: 0
},
by_quadrant: byQuadrant,
top_actions: byAction
};
}
/**
* Delete old logs (retention policy)
* @param {number} days - Keep logs newer than this many days
*/
static async deleteOldLogs(days = 90) {
const collection = await getCollection('governance_logs');
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - days);
const result = await collection.deleteMany({
created_at: { $lt: cutoffDate }
});
return result.deletedCount;
}
}
module.exports = GovernanceLog;