← back to Rentv Adintel
scripts/audit-source-policies.js
162 lines
#!/usr/bin/env node
'use strict';
/**
* CLI: node scripts/audit-source-policies.js
*
* Loads all source_policies from the database, runs
* lib/compliance/source-policy.validateSourcePolicy on each, and prints a
* PASS/FAIL table. Exits with code 1 if any ENABLED policy is illegal.
*
* Also validates the structural integrity of each policy regardless of its
* enabled state so developers can catch problems before enabling.
*
* @module scripts/audit-source-policies
*/
require('../lib/env');
const { pool, query } = require('../db');
let validateSourcePolicy;
let assertSourceEnabledLegal;
try {
({ validateSourcePolicy, assertSourceEnabledLegal } =
require('../lib/compliance/source-policy'));
} catch (err) {
console.error('[audit-sources] Could not load source-policy validator:', err.message);
process.exit(1);
}
/**
* Map DB row snake_case to the JS camelCase shape expected by validateSourcePolicy.
* @param {Object} row
* @returns {Object}
*/
function rowToPolicy(row) {
return {
sourceKey: row.source_key,
displayName: row.display_name,
owner: row.owner,
baseUrl: row.base_url,
accessMethod: row.access_method,
allowsAutomatedAccess: row.allows_automated_access,
allowsScreenshotCapture: row.allows_screenshot_capture,
allowsInternalStorage: row.allows_internal_storage,
allowsExport: row.allows_export,
prohibitedHosts: row.prohibited_hosts,
permittedPaths: row.permitted_paths,
prohibitedPaths: row.prohibited_paths,
maxRequestsPerMinute: row.max_requests_per_minute,
minimumDelayMs: row.minimum_delay_ms,
retentionDays: row.retention_days,
enabled: row.enabled,
reviewedAt: row.reviewed_at,
reviewNotes: row.review_notes,
};
}
(async () => {
let exitCode = 0;
try {
const res = await query(
`SELECT * FROM source_policies ORDER BY enabled DESC, display_name`
);
const rows = res.rows;
if (rows.length === 0) {
console.log('[audit-sources] No source policies found in the database.');
console.log('[audit-sources] Run db:seed to load initial policies.');
process.exit(0);
}
// Table header
const COL_KEY = 36;
const COL_ENABLED = 9;
const COL_RESULT = 8;
const hr = '-'.repeat(COL_KEY + COL_ENABLED + COL_RESULT + 40);
console.log('\nSource Policy Audit');
console.log(hr);
console.log(
'SOURCE_KEY'.padEnd(COL_KEY) +
'ENABLED'.padEnd(COL_ENABLED) +
'RESULT'.padEnd(COL_RESULT) +
'NOTES'
);
console.log(hr);
const enabledIllegal = [];
const allResults = [];
for (const row of rows) {
const policy = rowToPolicy(row);
const { valid, errors } = validateSourcePolicy(policy);
// Also check the harder assert for enabled policies
let assertErr = null;
if (policy.enabled) {
try {
assertSourceEnabledLegal(policy);
} catch (e) {
assertErr = e.message;
valid === false; // already false if errors exist
}
}
const isIllegal = !valid || !!assertErr;
const result = isIllegal ? 'FAIL' : 'PASS';
if (isIllegal && policy.enabled) {
enabledIllegal.push({ policy, errors, assertErr });
}
const notes = [
...errors,
assertErr ? `[ASSERT] ${assertErr}` : null,
].filter(Boolean).join('; ') || (valid ? 'OK' : 'see errors');
const resultTag = isIllegal ? `\x1b[31m${result}\x1b[0m` : `\x1b[32m${result}\x1b[0m`;
console.log(
(policy.sourceKey || '(no key)').slice(0, COL_KEY - 1).padEnd(COL_KEY) +
String(policy.enabled).padEnd(COL_ENABLED) +
resultTag.padEnd(COL_RESULT + 10) + // +10 for ANSI codes
notes.slice(0, 120)
);
allResults.push({ sourceKey: policy.sourceKey, valid, errors, enabled: policy.enabled });
}
console.log(hr);
const passed = allResults.filter((r) => r.valid).length;
const failed = allResults.length - passed;
const enabledFailed = enabledIllegal.length;
console.log(`\nSummary: ${rows.length} policies — ${passed} PASS, ${failed} FAIL`);
if (enabledIllegal.length > 0) {
console.error(`\n\x1b[31mERROR: ${enabledFailed} ENABLED policy/policies are ILLEGAL (spec §6):\x1b[0m`);
for (const { policy, errors, assertErr } of enabledIllegal) {
console.error(` SOURCE: ${policy.sourceKey}`);
for (const e of errors) console.error(` - ${e}`);
if (assertErr) console.error(` - [ASSERT] ${assertErr}`);
}
console.error('\nDisable or correct these policies before enabling research automation.');
exitCode = 1;
} else if (failed > 0) {
console.log(`\n${failed} policy/policies have validation issues but are not enabled.`);
console.log('No action required until you enable them.');
} else {
console.log('\nAll enabled policies are compliant with spec §6.');
}
} catch (err) {
console.error('[audit-sources] Fatal error:', err.message);
console.error(err.stack);
exitCode = 1;
} finally {
try { await pool.end(); } catch (_) {}
process.exit(exitCode);
}
})();