From 5ca2777815a69609d26d1f38c9c239bdff7d60c3 Mon Sep 17 00:00:00 2001 From: TheFlow Date: Tue, 21 Oct 2025 22:06:43 +1300 Subject: [PATCH] refactor: remove project-specific code and fix broken imports (Phase 7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL FIX: src/routes/index.js was importing 10 non-existent route files - Repository would CRASH ON STARTUP REMOVED (8 files): - src/config/currencies.config.js - Koha donation system (10 currencies, exchange rates) - src/routes/hooks-metrics.routes.js - Required deleted auth.middleware - src/routes/sync-health.routes.js - Required deleted auth.middleware - src/utils/security-logger.js - Hardcoded /var/log/tractatus paths, OUR inst_046 - scripts/seed-admin.js - Required deleted User.model - scripts/validate-deployment.js - OUR deployment validation (inst_025) - systemd/tractatus-dev.service - OUR server at /var/www/tractatus - systemd/tractatus-prod.service - OUR production server config REWRITTEN (2 files): src/routes/index.js - Removed imports: auth, documents, blog, newsletter, media, cases, admin, koha, demo, test - Removed imports: hooks-metrics, sync-health (just deleted) - Keep only: rules, projects, audit, governance (framework routes) - Removed website endpoint documentation - Updated to framework v3.5.0 src/config/app.config.js - Removed: JWT config (auth system deleted) - Removed: admin.email = john.stroh.nz@pm.me (hardcoded project-specific) - Removed: features.aiCuration/mediaTriage/caseSubmissions (website features) - Keep only: server, mongodb, logging, security (rate limiting), CORS - Now generic template for implementers RESULT: Repository can now start without errors, all imports resolve πŸ€– Generated with Claude Code Co-Authored-By: Claude --- scripts/seed-admin.js | 113 ------------ scripts/validate-deployment.js | 236 ------------------------ src/config/app.config.js | 21 +-- src/config/currencies.config.js | 277 ----------------------------- src/routes/hooks-metrics.routes.js | 49 ----- src/routes/index.js | 164 +++++------------ src/routes/sync-health.routes.js | 124 ------------- src/utils/security-logger.js | 72 -------- systemd/tractatus-dev.service | 41 ----- systemd/tractatus-prod.service | 41 ----- 10 files changed, 46 insertions(+), 1092 deletions(-) delete mode 100755 scripts/seed-admin.js delete mode 100755 scripts/validate-deployment.js delete mode 100644 src/config/currencies.config.js delete mode 100644 src/routes/hooks-metrics.routes.js delete mode 100644 src/routes/sync-health.routes.js delete mode 100644 src/utils/security-logger.js delete mode 100644 systemd/tractatus-dev.service delete mode 100644 systemd/tractatus-prod.service diff --git a/scripts/seed-admin.js b/scripts/seed-admin.js deleted file mode 100755 index 26e1ef44..00000000 --- a/scripts/seed-admin.js +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env node -/** - * Seed Admin User Script - * Creates the initial admin user for the Tractatus platform - * - * Usage: npm run seed:admin - */ - -require('dotenv').config(); - -const readline = require('readline'); -const { connect, close } = require('../src/utils/db.util'); -const User = require('../src/models/User.model'); -const logger = require('../src/utils/logger.util'); - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout -}); - -function question(prompt) { - return new Promise((resolve) => { - rl.question(prompt, resolve); - }); -} - -async function seedAdmin() { - try { - console.log('\n=== Tractatus Admin User Setup ===\n'); - - // Connect to database - await connect(); - - // Check if admin user already exists - const existingAdmin = await User.findByEmail(process.env.ADMIN_EMAIL || 'admin@tractatus.local'); - - if (existingAdmin) { - console.log('⚠️ Admin user already exists.'); - const overwrite = await question('Do you want to delete and recreate? (yes/no): '); - - if (overwrite.toLowerCase() !== 'yes') { - console.log('Cancelled. No changes made.'); - await cleanup(); - return; - } - - await User.deleteOne({ _id: existingAdmin._id }); - console.log('βœ… Existing admin user deleted.'); - } - - // Get admin details - console.log('\nEnter admin user details:'); - const name = await question('Name (default: Admin User): ') || 'Admin User'; - const email = await question(`Email (default: ${process.env.ADMIN_EMAIL || 'admin@tractatus.local'}): `) - || process.env.ADMIN_EMAIL - || 'admin@tractatus.local'; - - // Password input (hidden) - console.log('\n⚠️ Password will be visible. Use a development password only.'); - const password = await question('Password (min 8 chars): '); - - if (!password || password.length < 8) { - console.error('❌ Password must be at least 8 characters.'); - await cleanup(); - return; - } - - // Create admin user - const admin = await User.create({ - name, - email, - password, // Will be hashed by the model - role: 'admin', - active: true - }); - - console.log('\nβœ… Admin user created successfully!'); - console.log('\nCredentials:'); - console.log(` Email: ${admin.email}`); - console.log(` Role: ${admin.role}`); - console.log(` ID: ${admin._id}`); - console.log('\nYou can now login at: POST /api/auth/login'); - console.log(''); - - logger.info(`Admin user created: ${admin.email}`); - - } catch (error) { - console.error('\n❌ Error creating admin user:', error.message); - logger.error('Admin seed error:', error); - process.exit(1); - } finally { - await cleanup(); - } -} - -async function cleanup() { - rl.close(); - await close(); -} - -// Handle Ctrl+C -process.on('SIGINT', async () => { - console.log('\n\nπŸ‘‹ Cancelled by user'); - await cleanup(); - process.exit(0); -}); - -// Run if called directly -if (require.main === module) { - seedAdmin(); -} - -module.exports = seedAdmin; diff --git a/scripts/validate-deployment.js b/scripts/validate-deployment.js deleted file mode 100755 index 0369d12e..00000000 --- a/scripts/validate-deployment.js +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/bin/env node - -/** - * Pre-Deployment Validation - * - * Validates rsync/scp/deployment commands against inst_025 rules: - * - Checks if source files have different subdirectories - * - Ensures separate commands for different directory levels - * - Prevents directory structure flattening - * - * Usage: - * node scripts/validate-deployment.js --command "rsync ..." - * node scripts/validate-deployment.js --files "file1 file2" --target "remote:path" - * - * Exit codes: - * 0 = Valid deployment - * 1 = Invalid (violates inst_025) - * 2 = Error - * - * Copyright 2025 Tractatus Project - * Licensed under Apache License 2.0 - */ - -const fs = require('fs'); -const path = require('path'); - -/** - * Parse rsync command to extract source files and target - */ -function parseRsyncCommand(command) { - // Match rsync with options and files - const rsyncPattern = /rsync\s+([^"'\s]+(?:\s+[^"'\s]+)*)\s+((?:[^\s]+\s+)*)([\w@.-]+:[^\s]+|[^\s]+)$/; - const match = command.match(rsyncPattern); - - if (!match) { - return null; - } - - // Extract flags and files - const parts = command.split(/\s+/).filter(p => p.length > 0); - const rsyncIndex = parts.findIndex(p => p === 'rsync' || p.endsWith('/rsync')); - - if (rsyncIndex === -1) { - return null; - } - - const filesAndTarget = parts.slice(rsyncIndex + 1); - - // Last item is target - const target = filesAndTarget[filesAndTarget.length - 1]; - - // Everything before target that's not a flag is a file - const files = []; - for (let i = 0; i < filesAndTarget.length - 1; i++) { - const item = filesAndTarget[i]; - if (!item.startsWith('-')) { - files.push(item); - } - } - - return { - files, - target, - command - }; -} - -/** - * Check if files have different subdirectory paths - */ -function checkDirectoryMismatch(files) { - if (files.length <= 1) { - return { hasMismatch: false, directories: [] }; - } - - const directories = files.map(f => { - const dir = path.dirname(f); - return dir === '.' ? '' : dir; - }); - - const uniqueDirs = [...new Set(directories)]; - - return { - hasMismatch: uniqueDirs.length > 1, - directories: uniqueDirs, - filesByDir: Object.fromEntries( - uniqueDirs.map(dir => [ - dir, - files.filter(f => path.dirname(f) === (dir || '.')) - ]) - ) - }; -} - -/** - * Validate deployment command - */ -function validateDeployment(command) { - const parsed = parseRsyncCommand(command); - - if (!parsed) { - return { - valid: false, - error: 'Could not parse rsync command', - suggestion: null - }; - } - - const { files, target } = parsed; - - if (files.length === 0) { - return { - valid: false, - error: 'No source files specified', - suggestion: null - }; - } - - // Check for directory mismatch - const dirCheck = checkDirectoryMismatch(files); - - if (!dirCheck.hasMismatch) { - return { - valid: true, - message: 'Deployment command is valid - all files in same directory', - files, - target - }; - } - - // Violation detected - return { - valid: false, - error: `inst_025 violation: Files from different subdirectories in single rsync command`, - details: { - file_count: files.length, - unique_directories: dirCheck.directories.length, - directories: dirCheck.directories, - filesByDir: dirCheck.filesByDir - }, - suggestion: generateSeparateCommands(dirCheck.filesByDir, target, command) - }; -} - -/** - * Generate separate rsync commands for each directory - */ -function generateSeparateCommands(filesByDir, target, originalCommand) { - const commands = []; - - // Extract rsync flags from original command - const flagMatch = originalCommand.match(/rsync\s+([^/\s][^\s]*)/); - const flags = flagMatch ? flagMatch[1] : '-avz --progress'; - - Object.entries(filesByDir).forEach(([dir, files]) => { - const targetWithDir = dir ? `${target}/${dir}/` : target; - - files.forEach(file => { - const cmd = `rsync ${flags} ${file} ${targetWithDir}`; - commands.push(cmd); - }); - }); - - return commands; -} - -/** - * Display validation results - */ -function displayResults(result) { - if (result.valid) { - console.log('\x1b[32mβœ… Deployment command is VALID\x1b[0m'); - console.log(` Files: ${result.files.length}`); - console.log(` Target: ${result.target}`); - return 0; - } - - console.log('\x1b[31m❌ Deployment command VIOLATES inst_025\x1b[0m'); - console.log(`\n Error: ${result.error}\n`); - - if (result.details) { - console.log(' Details:'); - console.log(` File count: ${result.details.file_count}`); - console.log(` Unique directories: ${result.details.unique_directories}`); - console.log(' Directories:'); - result.details.directories.forEach(dir => { - const dirDisplay = dir || '(root)'; - const fileCount = result.details.filesByDir[dir].length; - console.log(` β€’ ${dirDisplay} (${fileCount} files)`); - result.details.filesByDir[dir].forEach(file => { - console.log(` - ${path.basename(file)}`); - }); - }); - } - - if (result.suggestion) { - console.log('\n \x1b[33mSuggested fix (separate commands per directory):\x1b[0m\n'); - result.suggestion.forEach((cmd, i) => { - console.log(` ${i + 1}. ${cmd}`); - }); - console.log(''); - } - - return 1; -} - -/** - * Main - */ -function main() { - const args = process.argv.slice(2); - - if (args.length === 0 || args.includes('--help')) { - console.log('Pre-Deployment Validation'); - console.log('\nUsage:'); - console.log(' node scripts/validate-deployment.js --command "rsync ..."'); - console.log('\nExample:'); - console.log(' node scripts/validate-deployment.js --command "rsync -avz file1 file2/sub/file remote:path"'); - process.exit(0); - } - - const commandIndex = args.indexOf('--command'); - - if (commandIndex === -1 || !args[commandIndex + 1]) { - console.error('Error: --command flag required'); - process.exit(2); - } - - const command = args[commandIndex + 1]; - const result = validateDeployment(command); - const exitCode = displayResults(result); - - process.exit(exitCode); -} - -main(); diff --git a/src/config/app.config.js b/src/config/app.config.js index 11478efa..d1ed32e4 100644 --- a/src/config/app.config.js +++ b/src/config/app.config.js @@ -1,12 +1,13 @@ /** * Application Configuration + * Generic configuration template for Tractatus Framework implementations */ module.exports = { // Server port: process.env.PORT || 9000, env: process.env.NODE_ENV || 'development', - appName: process.env.APP_NAME || 'Tractatus', + appName: process.env.APP_NAME || 'Tractatus Framework', // MongoDB mongodb: { @@ -14,30 +15,12 @@ module.exports = { db: process.env.MONGODB_DB || 'tractatus_dev' }, - // JWT - jwt: { - secret: process.env.JWT_SECRET || 'CHANGE_THIS_IN_PRODUCTION', - expiry: process.env.JWT_EXPIRY || '7d' - }, - - // Admin - admin: { - email: process.env.ADMIN_EMAIL || 'john.stroh.nz@pm.me' - }, - // Logging logging: { level: process.env.LOG_LEVEL || 'info', file: process.env.LOG_FILE || 'logs/app.log' }, - // Feature Flags - features: { - aiCuration: process.env.ENABLE_AI_CURATION === 'true', - mediaTriage: process.env.ENABLE_MEDIA_TRIAGE === 'true', - caseSubmissions: process.env.ENABLE_CASE_SUBMISSIONS === 'true' - }, - // Security security: { rateLimitWindowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000'), // 15 min diff --git a/src/config/currencies.config.js b/src/config/currencies.config.js deleted file mode 100644 index f8bae620..00000000 --- a/src/config/currencies.config.js +++ /dev/null @@ -1,277 +0,0 @@ -/** - * Currency Configuration - * Multi-currency support for Koha donation system - * - * Exchange rates based on NZD (New Zealand Dollar) as base currency - * Update rates periodically or use live API - */ - -// Base prices in NZD (in cents) -const BASE_PRICES_NZD = { - tier_5: 500, // $5 NZD - tier_15: 1500, // $15 NZD - tier_50: 5000 // $50 NZD -}; - -// Exchange rates: 1 NZD = X currency -// Last updated: 2025-10-08 -// Source: Manual calculation based on typical rates -const EXCHANGE_RATES = { - NZD: 1.0, // New Zealand Dollar (base) - USD: 0.60, // US Dollar - EUR: 0.55, // Euro - GBP: 0.47, // British Pound - AUD: 0.93, // Australian Dollar - CAD: 0.82, // Canadian Dollar - JPY: 94.0, // Japanese Yen - CHF: 0.53, // Swiss Franc - SGD: 0.81, // Singapore Dollar - HKD: 4.68 // Hong Kong Dollar -}; - -// Currency metadata (symbols, formatting, names) -const CURRENCY_CONFIG = { - NZD: { - symbol: '$', - code: 'NZD', - name: 'NZ Dollar', - decimals: 2, - locale: 'en-NZ', - flag: 'πŸ‡³πŸ‡Ώ' - }, - USD: { - symbol: '$', - code: 'USD', - name: 'US Dollar', - decimals: 2, - locale: 'en-US', - flag: 'πŸ‡ΊπŸ‡Έ' - }, - EUR: { - symbol: '€', - code: 'EUR', - name: 'Euro', - decimals: 2, - locale: 'de-DE', - flag: 'πŸ‡ͺπŸ‡Ί' - }, - GBP: { - symbol: 'Β£', - code: 'GBP', - name: 'British Pound', - decimals: 2, - locale: 'en-GB', - flag: 'πŸ‡¬πŸ‡§' - }, - AUD: { - symbol: '$', - code: 'AUD', - name: 'Australian Dollar', - decimals: 2, - locale: 'en-AU', - flag: 'πŸ‡¦πŸ‡Ί' - }, - CAD: { - symbol: '$', - code: 'CAD', - name: 'Canadian Dollar', - decimals: 2, - locale: 'en-CA', - flag: 'πŸ‡¨πŸ‡¦' - }, - JPY: { - symbol: 'Β₯', - code: 'JPY', - name: 'Japanese Yen', - decimals: 0, // JPY has no decimal places - locale: 'ja-JP', - flag: 'πŸ‡―πŸ‡΅' - }, - CHF: { - symbol: 'CHF', - code: 'CHF', - name: 'Swiss Franc', - decimals: 2, - locale: 'de-CH', - flag: 'πŸ‡¨πŸ‡­' - }, - SGD: { - symbol: '$', - code: 'SGD', - name: 'Singapore Dollar', - decimals: 2, - locale: 'en-SG', - flag: 'πŸ‡ΈπŸ‡¬' - }, - HKD: { - symbol: '$', - code: 'HKD', - name: 'Hong Kong Dollar', - decimals: 2, - locale: 'zh-HK', - flag: 'πŸ‡­πŸ‡°' - } -}; - -// Supported currencies list (in display order) -const SUPPORTED_CURRENCIES = [ - 'NZD', // Default - 'USD', - 'EUR', - 'GBP', - 'AUD', - 'CAD', - 'JPY', - 'CHF', - 'SGD', - 'HKD' -]; - -/** - * Convert NZD amount to target currency - * @param {number} amountNZD - Amount in NZD cents - * @param {string} targetCurrency - Target currency code - * @returns {number} - Amount in target currency cents - */ -function convertFromNZD(amountNZD, targetCurrency) { - const currency = targetCurrency.toUpperCase(); - - if (!EXCHANGE_RATES[currency]) { - throw new Error(`Unsupported currency: ${targetCurrency}`); - } - - const rate = EXCHANGE_RATES[currency]; - const converted = Math.round(amountNZD * rate); - - return converted; -} - -/** - * Convert any currency amount to NZD - * @param {number} amount - Amount in source currency cents - * @param {string} sourceCurrency - Source currency code - * @returns {number} - Amount in NZD cents - */ -function convertToNZD(amount, sourceCurrency) { - const currency = sourceCurrency.toUpperCase(); - - if (!EXCHANGE_RATES[currency]) { - throw new Error(`Unsupported currency: ${sourceCurrency}`); - } - - const rate = EXCHANGE_RATES[currency]; - const nzdAmount = Math.round(amount / rate); - - return nzdAmount; -} - -/** - * Get tier prices for a specific currency - * @param {string} currency - Currency code - * @returns {object} - Tier prices in target currency (cents) - */ -function getTierPrices(currency) { - const tier5 = convertFromNZD(BASE_PRICES_NZD.tier_5, currency); - const tier15 = convertFromNZD(BASE_PRICES_NZD.tier_15, currency); - const tier50 = convertFromNZD(BASE_PRICES_NZD.tier_50, currency); - - return { - tier_5: tier5, - tier_15: tier15, - tier_50: tier50 - }; -} - -/** - * Format currency amount for display - * @param {number} amountCents - Amount in cents - * @param {string} currency - Currency code - * @returns {string} - Formatted currency string (e.g., "$15.00", "Β₯1,400") - */ -function formatCurrency(amountCents, currency) { - const config = CURRENCY_CONFIG[currency.toUpperCase()]; - - if (!config) { - throw new Error(`Unsupported currency: ${currency}`); - } - - const amount = amountCents / 100; // Convert cents to dollars - - return new Intl.NumberFormat(config.locale, { - style: 'currency', - currency: currency.toUpperCase(), - minimumFractionDigits: config.decimals, - maximumFractionDigits: config.decimals - }).format(amount); -} - -/** - * Get currency display name with flag - * @param {string} currency - Currency code - * @returns {string} - Display name (e.g., "πŸ‡ΊπŸ‡Έ USD - US Dollar") - */ -function getCurrencyDisplayName(currency) { - const config = CURRENCY_CONFIG[currency.toUpperCase()]; - - if (!config) { - return currency.toUpperCase(); - } - - return `${config.flag} ${config.code} - ${config.name}`; -} - -/** - * Validate currency code - * @param {string} currency - Currency code - * @returns {boolean} - True if supported - */ -function isSupportedCurrency(currency) { - return SUPPORTED_CURRENCIES.includes(currency.toUpperCase()); -} - -/** - * Get exchange rate for a currency - * @param {string} currency - Currency code - * @returns {number} - Exchange rate (1 NZD = X currency) - */ -function getExchangeRate(currency) { - return EXCHANGE_RATES[currency.toUpperCase()] || null; -} - -/** - * Detect currency from user location - * This is a simplified version - in production, use IP geolocation API - * @param {string} countryCode - ISO country code (e.g., 'US', 'GB') - * @returns {string} - Suggested currency code - */ -function getCurrencyForCountry(countryCode) { - const countryToCurrency = { - 'NZ': 'NZD', - 'US': 'USD', - 'DE': 'EUR', 'FR': 'EUR', 'IT': 'EUR', 'ES': 'EUR', 'NL': 'EUR', - 'GB': 'GBP', - 'AU': 'AUD', - 'CA': 'CAD', - 'JP': 'JPY', - 'CH': 'CHF', - 'SG': 'SGD', - 'HK': 'HKD' - }; - - return countryToCurrency[countryCode.toUpperCase()] || 'NZD'; // Default to NZD -} - -module.exports = { - BASE_PRICES_NZD, - EXCHANGE_RATES, - CURRENCY_CONFIG, - SUPPORTED_CURRENCIES, - convertFromNZD, - convertToNZD, - getTierPrices, - formatCurrency, - getCurrencyDisplayName, - isSupportedCurrency, - getExchangeRate, - getCurrencyForCountry -}; diff --git a/src/routes/hooks-metrics.routes.js b/src/routes/hooks-metrics.routes.js deleted file mode 100644 index 475269ec..00000000 --- a/src/routes/hooks-metrics.routes.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Hooks Metrics API Routes - * Serves framework enforcement metrics to admin dashboard - */ - -const express = require('express'); -const router = express.Router(); -const fs = require('fs'); -const path = require('path'); -const { authenticateToken, requireAdmin } = require('../middleware/auth.middleware'); - -const METRICS_PATH = path.join(__dirname, '../../.claude/metrics/hooks-metrics.json'); - -/** - * GET /api/admin/hooks/metrics - * Get current hooks metrics - */ -router.get('/metrics', authenticateToken, requireAdmin, async (req, res) => { - try { - // Check if metrics file exists - if (!fs.existsSync(METRICS_PATH)) { - return res.json({ - success: true, - metrics: { - hook_executions: [], - blocks: [], - session_stats: {} - } - }); - } - - // Read metrics - const metricsData = fs.readFileSync(METRICS_PATH, 'utf8'); - const metrics = JSON.parse(metricsData); - - res.json({ - success: true, - metrics: metrics - }); - } catch (error) { - console.error('Error fetching hooks metrics:', error); - res.status(500).json({ - success: false, - error: 'Failed to fetch hooks metrics' - }); - } -}); - -module.exports = router; diff --git a/src/routes/index.js b/src/routes/index.js index e1570869..903e86c3 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,147 +1,71 @@ /** * Routes Index - * Central routing configuration + * Central routing configuration for Tractatus Framework API */ const express = require('express'); const router = express.Router(); -// Import route modules -const authRoutes = require('./auth.routes'); -const documentsRoutes = require('./documents.routes'); -const blogRoutes = require('./blog.routes'); -const newsletterRoutes = require('./newsletter.routes'); -const mediaRoutes = require('./media.routes'); -const casesRoutes = require('./cases.routes'); -const adminRoutes = require('./admin.routes'); -const hooksMetricsRoutes = require('./hooks-metrics.routes'); -const syncHealthRoutes = require('./sync-health.routes'); +// Import framework route modules const rulesRoutes = require('./rules.routes'); const projectsRoutes = require('./projects.routes'); const auditRoutes = require('./audit.routes'); const governanceRoutes = require('./governance.routes'); -const kohaRoutes = require('./koha.routes'); -const demoRoutes = require('./demo.routes'); -// Development/test routes (only in development) -if (process.env.NODE_ENV !== 'production') { - const testRoutes = require('./test.routes'); - router.use('/test', testRoutes); -} - -// Mount routes -router.use('/auth', authRoutes); -router.use('/documents', documentsRoutes); -router.use('/blog', blogRoutes); -router.use('/newsletter', newsletterRoutes); -router.use('/media', mediaRoutes); -router.use('/cases', casesRoutes); -router.use('/admin', adminRoutes); -router.use('/admin/hooks', hooksMetricsRoutes); -router.use('/admin/sync', syncHealthRoutes); -router.use('/admin/rules', rulesRoutes); -router.use('/admin/projects', projectsRoutes); -router.use('/admin', auditRoutes); +// Mount framework routes +router.use('/rules', rulesRoutes); +router.use('/projects', projectsRoutes); +router.use('/audit', auditRoutes); router.use('/governance', governanceRoutes); -router.use('/koha', kohaRoutes); -router.use('/demo', demoRoutes); -// API root endpoint - redirect browsers to documentation +// API root endpoint router.get('/', (req, res) => { - // Check if request is from a browser (Accept: text/html) - const acceptsHtml = req.accepts('html'); - const acceptsJson = req.accepts('json'); - - // If browser request, redirect to API documentation page - if (acceptsHtml && !acceptsJson) { - return res.redirect(302, '/api-reference.html'); - } - res.json({ name: 'Tractatus AI Safety Framework API', - version: '1.0.0', + version: '3.5.0', status: 'operational', + documentation: 'https://agenticgovernance.digital', endpoints: { - auth: { - login: 'POST /api/auth/login', - me: 'GET /api/auth/me', - logout: 'POST /api/auth/logout' - }, - documents: { - list: 'GET /api/documents', - get: 'GET /api/documents/:identifier', - search: 'GET /api/documents/search?q=query', - create: 'POST /api/documents (admin)', - update: 'PUT /api/documents/:id (admin)', - delete: 'DELETE /api/documents/:id (admin)' - }, - blog: { - list: 'GET /api/blog', - get: 'GET /api/blog/:slug', - create: 'POST /api/blog (admin)', - update: 'PUT /api/blog/:id (admin)', - publish: 'POST /api/blog/:id/publish (admin)', - delete: 'DELETE /api/blog/:id (admin)', - admin_list: 'GET /api/blog/admin/posts?status=draft (admin)', - admin_get: 'GET /api/blog/admin/:id (admin)', - suggest_topics: 'POST /api/blog/suggest-topics (admin)' - }, - newsletter: { - subscribe: 'POST /api/newsletter/subscribe', - verify: 'GET /api/newsletter/verify/:token', - unsubscribe: 'POST /api/newsletter/unsubscribe', - preferences: 'PUT /api/newsletter/preferences', - stats: 'GET /api/newsletter/admin/stats (admin)', - subscriptions: 'GET /api/newsletter/admin/subscriptions (admin)', - export: 'GET /api/newsletter/admin/export (admin)', - delete: 'DELETE /api/newsletter/admin/subscriptions/:id (admin)' - }, - media: { - submit: 'POST /api/media/inquiries', - list: 'GET /api/media/inquiries (admin)', - urgent: 'GET /api/media/inquiries/urgent (admin)', - get: 'GET /api/media/inquiries/:id (admin)', - assign: 'POST /api/media/inquiries/:id/assign (admin)', - respond: 'POST /api/media/inquiries/:id/respond (admin)', - delete: 'DELETE /api/media/inquiries/:id (admin)' - }, - cases: { - submit: 'POST /api/cases/submit', - list: 'GET /api/cases/submissions (admin)', - high_relevance: 'GET /api/cases/submissions/high-relevance (admin)', - get: 'GET /api/cases/submissions/:id (admin)', - approve: 'POST /api/cases/submissions/:id/approve (admin)', - reject: 'POST /api/cases/submissions/:id/reject (admin)', - request_info: 'POST /api/cases/submissions/:id/request-info (admin)', - delete: 'DELETE /api/cases/submissions/:id (admin)' - }, - admin: { - moderation_queue: 'GET /api/admin/moderation', - moderation_item: 'GET /api/admin/moderation/:id', - review: 'POST /api/admin/moderation/:id/review', - stats: 'GET /api/admin/stats', - activity: 'GET /api/admin/activity' - }, governance: { status: 'GET /api/governance', - classify: 'POST /api/governance/classify (admin)', - validate: 'POST /api/governance/validate (admin)', - enforce: 'POST /api/governance/enforce (admin)', - pressure: 'POST /api/governance/pressure (admin)', - verify: 'POST /api/governance/verify (admin)' + classify: 'POST /api/governance/classify', + validate: 'POST /api/governance/validate', + enforce: 'POST /api/governance/enforce', + pressure: 'POST /api/governance/pressure', + verify: 'POST /api/governance/verify' }, - koha: { - checkout: 'POST /api/koha/checkout', - webhook: 'POST /api/koha/webhook', - transparency: 'GET /api/koha/transparency', - cancel: 'POST /api/koha/cancel', - verify: 'GET /api/koha/verify/:sessionId', - statistics: 'GET /api/koha/statistics (admin)' + rules: { + list: 'GET /api/rules', + get: 'GET /api/rules/:id', + create: 'POST /api/rules', + update: 'PUT /api/rules/:id', + delete: 'DELETE /api/rules/:id', + search: 'GET /api/rules/search' + }, + projects: { + list: 'GET /api/projects', + get: 'GET /api/projects/:id', + create: 'POST /api/projects', + update: 'PUT /api/projects/:id', + delete: 'DELETE /api/projects/:id' + }, + audit: { + logs: 'GET /api/audit/logs', + stats: 'GET /api/audit/stats' } }, - framework: 'Tractatus-Based LLM Safety Architecture', - documentation: '/api/docs', - health: '/health' + framework: { + name: 'Tractatus Framework', + description: 'AI governance framework enforcing architectural safety constraints at runtime', + services: [ + 'InstructionPersistenceClassifier', + 'CrossReferenceValidator', + 'BoundaryEnforcer', + 'ContextPressureMonitor', + 'MetacognitiveVerifier', + 'PluralisticDeliberationOrchestrator' + ] + } }); }); diff --git a/src/routes/sync-health.routes.js b/src/routes/sync-health.routes.js deleted file mode 100644 index 80ec3eec..00000000 --- a/src/routes/sync-health.routes.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Sync Health Check Routes - * Monitors synchronization between file-based instructions and MongoDB - */ - -const express = require('express'); -const router = express.Router(); -const fs = require('fs'); -const path = require('path'); -const { authenticateToken, requireAdmin } = require('../middleware/auth.middleware'); -const GovernanceRule = require('../models/GovernanceRule.model'); - -const INSTRUCTION_FILE = path.join(__dirname, '../../.claude/instruction-history.json'); - -/** - * GET /api/admin/sync/health - * Check synchronization health between file and database - */ -router.get('/health', authenticateToken, requireAdmin, async (req, res) => { - try { - let fileInstructions = []; - let fileError = null; - - if (fs.existsSync(INSTRUCTION_FILE)) { - try { - const fileData = JSON.parse(fs.readFileSync(INSTRUCTION_FILE, 'utf8')); - fileInstructions = (fileData.instructions || []).filter(i => i.active !== false); - } catch (err) { - fileError = err.message; - } - } else { - fileError = 'File not found'; - } - - const dbRules = await GovernanceRule.find({ active: true }).lean(); - const fileCount = fileInstructions.length; - const dbCount = dbRules.length; - const difference = Math.abs(fileCount - dbCount); - const diffPercent = fileCount > 0 ? ((difference / fileCount) * 100).toFixed(1) : 0; - - let status = 'healthy'; - let message = 'File and database are synchronized'; - let severity = 'success'; - - if (fileError) { - status = 'error'; - message = 'Cannot read instruction file: ' + fileError; - severity = 'error'; - } else if (difference === 0) { - status = 'healthy'; - message = 'Perfectly synchronized'; - severity = 'success'; - } else if (difference <= 2) { - status = 'warning'; - message = 'Minor desync: ' + difference + ' instruction' + (difference !== 1 ? 's' : '') + ' differ'; - severity = 'warning'; - } else if (difference <= 5) { - status = 'warning'; - message = 'Moderate desync: ' + difference + ' instructions differ (' + diffPercent + '%)'; - severity = 'warning'; - } else { - status = 'critical'; - message = 'Critical desync: ' + difference + ' instructions differ (' + diffPercent + '%)'; - severity = 'error'; - } - - const fileIds = new Set(fileInstructions.map(i => i.id)); - const dbIds = new Set(dbRules.map(r => r.id)); - - const missingInDb = fileInstructions - .filter(i => !dbIds.has(i.id)) - .map(i => ({ id: i.id, text: i.text.substring(0, 60) + '...' })); - - const orphanedInDb = dbRules - .filter(r => !fileIds.has(r.id)) - .map(r => ({ id: r.id, text: r.text.substring(0, 60) + '...' })); - - res.json({ - success: true, - health: { - status, - message, - severity, - timestamp: new Date().toISOString(), - counts: { file: fileCount, database: dbCount, difference, differencePercent: parseFloat(diffPercent) }, - details: { missingInDatabase: missingInDb, orphanedInDatabase: orphanedInDb }, - recommendations: difference > 0 ? [ - 'Run: node scripts/sync-instructions-to-db.js --force', - 'Or restart the server (auto-sync on startup)', - 'Or wait for next session initialization' - ] : [] - } - }); - } catch (error) { - console.error('Sync health check error:', error); - res.status(500).json({ success: false, error: 'Failed to check sync health', message: error.message }); - } -}); - -/** - * POST /api/admin/sync/trigger - * Manually trigger synchronization - */ -router.post('/trigger', authenticateToken, requireAdmin, async (req, res) => { - try { - const { syncInstructions } = require('../../scripts/sync-instructions-to-db.js'); - const result = await syncInstructions({ silent: true }); - - if (result.success) { - res.json({ - success: true, - message: 'Synchronization completed successfully', - result: { added: result.added, updated: result.updated, deactivated: result.deactivated, finalCount: result.finalCount } - }); - } else { - res.status(500).json({ success: false, error: 'Synchronization failed', message: result.error || 'Unknown error' }); - } - } catch (error) { - console.error('Manual sync trigger error:', error); - res.status(500).json({ success: false, error: 'Failed to trigger synchronization', message: error.message }); - } -}); - -module.exports = router; diff --git a/src/utils/security-logger.js b/src/utils/security-logger.js deleted file mode 100644 index ef38955e..00000000 --- a/src/utils/security-logger.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Security Event Logger (inst_046 - Quick Win Version) - * Centralized logging for all security events - * - * QUICK WIN: Simple file-based logging with JSON format - * Full version in Phase 5 will add ProtonMail/Signal alerts - */ - -const fs = require('fs').promises; -const path = require('path'); - -const SECURITY_LOG_PATH = process.env.SECURITY_LOG_PATH || - (process.env.HOME ? `${process.env.HOME}/var/log/tractatus/security-audit.log` : '/var/log/tractatus/security-audit.log'); - -/** - * Log a security event to audit trail - * - * @param {Object} event - Security event details - * @param {string} event.type - Event type (e.g., 'rate_limit_violation') - * @param {string} event.sourceIp - Source IP address - * @param {string} event.userId - User ID (if authenticated) - * @param {string} event.endpoint - Request endpoint - * @param {string} event.userAgent - User agent string - * @param {Object} event.details - Additional event details - * @param {string} event.action - Action taken (e.g., 'blocked', 'logged') - * @param {string} event.severity - Severity level ('low', 'medium', 'high', 'critical') - */ -async function logSecurityEvent(event) { - const logEntry = { - timestamp: new Date().toISOString(), - event_type: event.type || 'unknown', - source_ip: event.sourceIp || 'unknown', - user_id: event.userId || 'anonymous', - endpoint: event.endpoint || 'unknown', - user_agent: event.userAgent || 'unknown', - violation_details: event.details || {}, - action_taken: event.action || 'logged', - severity: event.severity || 'medium' - }; - - const logLine = JSON.stringify(logEntry) + '\n'; - - try { - // Ensure log directory exists - const logDir = path.dirname(SECURITY_LOG_PATH); - await fs.mkdir(logDir, { recursive: true, mode: 0o750 }); - - // Append to log file - await fs.appendFile(SECURITY_LOG_PATH, logLine, { encoding: 'utf-8' }); - } catch (error) { - // Fallback to console if file logging fails - console.error('[SECURITY LOGGER ERROR]', error.message); - console.error('[SECURITY EVENT]', logEntry); - } -} - -/** - * Helper: Extract client IP from request (handles proxies) - */ -function getClientIp(req) { - return ( - req.ip || - req.headers['x-forwarded-for']?.split(',')[0]?.trim() || - req.connection.remoteAddress || - 'unknown' - ); -} - -module.exports = { - logSecurityEvent, - getClientIp -}; diff --git a/systemd/tractatus-dev.service b/systemd/tractatus-dev.service deleted file mode 100644 index f6f1b382..00000000 --- a/systemd/tractatus-dev.service +++ /dev/null @@ -1,41 +0,0 @@ -[Unit] -Description=Tractatus AI Safety Framework (Development) -Documentation=https://tractatus.sydigital.co.nz -After=network.target mongod.service -Wants=mongod.service - -[Service] -Type=simple -User=theflow -Group=theflow -WorkingDirectory=/home/theflow/projects/tractatus - -# Environment -Environment=NODE_ENV=development -Environment=PORT=9000 -EnvironmentFile=/home/theflow/projects/tractatus/.env - -# Execution -ExecStart=/usr/bin/node src/server.js -Restart=always -RestartSec=10 - -# Security hardening -NoNewPrivileges=true -PrivateTmp=true -ProtectSystem=strict -ProtectHome=read-only -ReadWritePaths=/home/theflow/projects/tractatus/logs -ReadWritePaths=/home/theflow/projects/tractatus/uploads - -# Logging -StandardOutput=journal -StandardError=journal -SyslogIdentifier=tractatus-dev - -# Resource limits -LimitNOFILE=65536 -MemoryLimit=1G - -[Install] -WantedBy=multi-user.target diff --git a/systemd/tractatus-prod.service b/systemd/tractatus-prod.service deleted file mode 100644 index a58fcb37..00000000 --- a/systemd/tractatus-prod.service +++ /dev/null @@ -1,41 +0,0 @@ -[Unit] -Description=Tractatus AI Safety Framework (Production) -Documentation=https://tractatus.sydigital.co.nz -After=network.target mongod.service -Wants=mongod.service - -[Service] -Type=simple -User=ubuntu -Group=ubuntu -WorkingDirectory=/var/www/tractatus - -# Environment -Environment=NODE_ENV=production -Environment=PORT=9000 -EnvironmentFile=/var/www/tractatus/.env - -# Execution -ExecStart=/usr/bin/node src/server.js -Restart=always -RestartSec=10 - -# Security hardening -NoNewPrivileges=true -PrivateTmp=true -ProtectSystem=strict -ProtectHome=true -ReadWritePaths=/var/www/tractatus/logs -ReadWritePaths=/var/www/tractatus/uploads - -# Logging -StandardOutput=journal -StandardError=journal -SyslogIdentifier=tractatus - -# Resource limits -LimitNOFILE=65536 -MemoryLimit=2G - -[Install] -WantedBy=multi-user.target