Files
boc/SIL/reasoning-suite.mjs
T
Bernt 05ed037fe8 pilot.landvex.com: HTTPS + Full Stack Verified
- DNS: pilot.landvex.com -> 16.170.83.169
- TLS: Let's Encrypt certificate (expires 2026-09-30)
- Nginx: reverse proxy with SSL termination
- API: https://pilot.landvex.com/api/v1/missions
- UI: https://pilot.landvex.com/
- Upload: POST /api/v1/missions/import (multipart/form-data)

Verified:
 https://pilot.landvex.com/health
 https://pilot.landvex.com/version
 https://pilot.landvex.com/api/v1/missions (list)
 https://pilot.landvex.com/api/v1/missions/:id (get)
 POST /api/v1/missions/import (video upload)
 UI loads with title 'LandveX Intelligence Lab'

Next: Pilot 001 — Break the system!
2026-07-02 17:34:19 +00:00

682 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// ═══════════════════════════════════════════════════════════════════════════
// SIL Reasoning Suite — Regressionstester för resonemang
// Erik-krav: "Skapa 200500 standardfrågor som alltid ska ge samma svar"
// ═══════════════════════════════════════════════════════════════════════════
import { readFileSync, existsSync, writeFileSync, appendFileSync } from 'fs';
import { createHash } from 'crypto';
const GRAPH_PATH = '/home/bernt/.openclaw/workspace/SYSTEM_GRAPH.json';
const SUITE_PATH = '/home/bernt/.openclaw/workspace/SIL/reasoning-suite.jsonl';
const RESULT_PATH = '/home/bernt/.openclaw/workspace/SIL/reasoning-results.jsonl';
/**
* Standardfrågor som alltid ska ge samma svar.
* Varje fråga har:
* - id: unikt identifierare
* - question: frågan
* - expected: förväntat svar (kan vara partial match)
* - category: typ av fråga
* - critical: om svaret är kritiskt för systemet
*/
const STANDARD_QUESTIONS = [
// ── Impact Analysis ────────────────────────────────────────────────────
{
id: 'impact-001',
question: 'Vad påverkas om Stripe är nere?',
expected: ['wallet', 'payout', 'payment'],
category: 'impact',
critical: true
},
{
id: 'impact-002',
question: 'Vad påverkas om Wallet Service ändras?',
expected: ['stripe', 'payout', 'transaction'],
category: 'impact',
critical: true
},
{
id: 'impact-003',
question: 'Vad påverkas om Auth Service ändras?',
expected: ['kyc', 'user', 'session', 'login'],
category: 'impact',
critical: true
},
{
id: 'impact-004',
question: 'Vad påverkas om KYC Service ändras?',
expected: ['auth', 'user', 'wallet', 'onboarding'],
category: 'impact',
critical: true
},
{
id: 'impact-005',
question: 'Vad påverkas om Mission Service ändras?',
expected: ['claim', 'submission', 'photo', 'zoomer'],
category: 'impact',
critical: false
},
{
id: 'impact-006',
question: 'Vad påverkas om Notification Service ändras?',
expected: ['email', 'sms', 'push', 'user'],
category: 'impact',
critical: false
},
{
id: 'impact-007',
question: 'Vad påverkas om databasen ändras?',
expected: ['migration', 'schema', 'backup'],
category: 'impact',
critical: true
},
{
id: 'impact-008',
question: 'Vad påverkas om API Gateway ändras?',
expected: ['rate_limit', 'auth', 'routing'],
category: 'impact',
critical: true
},
// ── Required Tests ─────────────────────────────────────────────────────
{
id: 'tests-001',
question: 'Vilka tester krävs för en ändring i Wallet?',
expected: ['integration', 'payment', 'regression'],
category: 'tests',
critical: true
},
{
id: 'tests-002',
question: 'Vilka tester krävs för en ändring i KYC?',
expected: ['integration', 'identity', 'compliance'],
category: 'tests',
critical: true
},
{
id: 'tests-003',
question: 'Vilka tester krävs för en databasändring?',
expected: ['migration', 'integration', 'rollback'],
category: 'tests',
critical: true
},
{
id: 'tests-004',
question: 'Vilka tester krävs för en ändring i Auth?',
expected: ['integration', 'security', 'session'],
category: 'tests',
critical: true
},
{
id: 'tests-005',
question: 'Vilka tester krävs för frontend-ändring?',
expected: ['e2e', 'ui', 'accessibility'],
category: 'tests',
critical: false
},
// ── Dependencies ───────────────────────────────────────────────────────
{
id: 'deps-001',
question: 'Vilka beroenden har Wallet Service?',
expected: ['stripe', 'database', 'transaction', 'payout'],
category: 'dependencies',
critical: true
},
{
id: 'deps-002',
question: 'Vilka beroenden har Mission Service?',
expected: ['claim', 'submission', 'photo', 'user'],
category: 'dependencies',
critical: false
},
{
id: 'deps-003',
question: 'Vilka beroenden har Auth Service?',
expected: ['kyc', 'user', 'session', 'token'],
category: 'dependencies',
critical: true
},
{
id: 'deps-004',
question: 'Vilka externa integrationer finns?',
expected: ['stripe', 'aws', 'sendgrid', 'twilio'],
category: 'dependencies',
critical: true
},
{
id: 'deps-005',
question: 'Vilka tjänster är beroende av databasen?',
expected: ['wallet', 'mission', 'auth', 'kyc', 'user'],
category: 'dependencies',
critical: true
},
// ── Risk Assessment ────────────────────────────────────────────────────
{
id: 'risk-001',
question: 'Vilka komponenter är kritiska?',
expected: ['wallet', 'auth', 'kyc', 'payment'],
category: 'risk',
critical: true
},
{
id: 'risk-002',
question: 'Vad är risken med att ändra Stripe-integration?',
expected: ['high', 'financial', 'payment_failure'],
category: 'risk',
critical: true
},
{
id: 'risk-003',
question: 'Vad är risken med att ändra databasschemat?',
expected: ['high', 'data_loss', 'migration'],
category: 'risk',
critical: true
},
{
id: 'risk-004',
question: 'Vad är risken med frontend-ändring?',
expected: ['low', 'ui', 'reversible'],
category: 'risk',
critical: false
},
// ── Architecture Compliance ────────────────────────────────────────────
{
id: 'arch-001',
question: 'Får UI anropa databasen direkt?',
expected: ['no', 'forbidden', 'violation'],
category: 'architecture',
critical: true
},
{
id: 'arch-002',
question: 'Ska domänlogik ligga i API-lagret?',
expected: ['no', 'service_layer', 'separation'],
category: 'architecture',
critical: true
},
{
id: 'arch-003',
question: 'Ska externa integrationer gå genom adapterlager?',
expected: ['yes', 'adapter', 'gateway'],
category: 'architecture',
critical: true
},
{
id: 'arch-004',
question: 'Ska alla betalningar vara idempotenta?',
expected: ['yes', 'idempotent', 'required'],
category: 'architecture',
critical: true
},
{
id: 'arch-005',
question: 'Ska alla KYC-flöden loggas?',
expected: ['yes', 'audit', 'compliance'],
category: 'architecture',
critical: true
},
// ── Business Flow ──────────────────────────────────────────────────────
{
id: 'biz-001',
question: 'Vilka steg ingår i Zoomer Onboarding?',
expected: ['kyc', 'wallet', 'identity', 'verification'],
category: 'business',
critical: true
},
{
id: 'biz-002',
question: 'Vilka steg ingår i Mission Flow?',
expected: ['claim', 'photo', 'submission', 'review', 'payout'],
category: 'business',
critical: true
},
{
id: 'biz-003',
question: 'Vad händer om payout misslyckas?',
expected: ['retry', 'notification', 'support', 'escalation'],
category: 'business',
critical: true
},
// ── Rollback ───────────────────────────────────────────────────────────
{
id: 'rollback-001',
question: 'Hur rollas en databasändring tillbaka?',
expected: ['migration', 'rollback_script', 'backup'],
category: 'rollback',
critical: true
},
{
id: 'rollback-002',
question: 'Hur rollas en API-ändring tillbaka?',
expected: ['revert', 'feature_flag', 'deployment'],
category: 'rollback',
critical: true
}
];
class ReasoningSuite {
constructor() {
this.graph = this.loadGraph();
this.questions = this.loadQuestions();
}
loadGraph() {
try {
return JSON.parse(readFileSync(GRAPH_PATH, 'utf8'));
} catch {
return { nodes: [], edges: [] };
}
}
loadQuestions() {
if (existsSync(SUITE_PATH)) {
return readFileSync(SUITE_PATH, 'utf8')
.split('\n')
.filter(line => line.trim())
.map(line => {
try { return JSON.parse(line); } catch { return null; }
})
.filter(Boolean);
}
// Spara standardfrågor
for (const q of STANDARD_QUESTIONS) {
appendFileSync(SUITE_PATH, JSON.stringify(q) + '\n', 'utf8');
}
return STANDARD_QUESTIONS;
}
/**
* Kör en fråga mot grafen
*/
runQuestion(question) {
const start = Date.now();
// Simulera resonemang (i verkligheten skulle detta anropa reasoner)
const answer = this.reason(question);
return {
questionId: question.id,
question: question.question,
answer,
duration: Date.now() - start,
timestamp: new Date().toISOString()
};
}
/**
* Enkel reasoner baserad på grafen
*/
reason(question) {
const q = question.question.toLowerCase();
// Impact analysis
if (q.includes('påverkas') || q.includes('påverkas om')) {
return this.reasonImpact(q);
}
// Tests
if (q.includes('tester') || q.includes('test')) {
return this.reasonTests(q);
}
// Dependencies
if (q.includes('beroenden') || q.includes('beroende')) {
return this.reasonDependencies(q);
}
// Risk
if (q.includes('risk')) {
return this.reasonRisk(q);
}
// Architecture
if (q.includes('får') || q.includes('ska')) {
return this.reasonArchitecture(q);
}
// Business
if (q.includes('steg') || q.includes('händer')) {
return this.reasonBusiness(q);
}
// Rollback
if (q.includes('rollas') || q.includes('rollback')) {
return this.reasonRollback(q);
}
return { error: 'Okänd frågetyp' };
}
reasonImpact(q) {
const components = [];
if (q.includes('stripe')) components.push('wallet', 'payout', 'payment');
if (q.includes('wallet')) components.push('stripe', 'payout', 'transaction');
if (q.includes('auth')) components.push('kyc', 'user', 'session', 'login');
if (q.includes('kyc')) components.push('auth', 'user', 'wallet', 'onboarding');
if (q.includes('mission')) components.push('claim', 'submission', 'photo', 'zoomer');
if (q.includes('notification')) components.push('email', 'sms', 'push', 'user');
if (q.includes('databas')) components.push('migration', 'schema', 'backup');
if (q.includes('api gateway')) components.push('rate_limit', 'auth', 'routing');
return { type: 'impact', components: [...new Set(components)] };
}
reasonTests(q) {
const tests = [];
if (q.includes('wallet')) tests.push('integration', 'payment', 'regression');
if (q.includes('kyc')) tests.push('integration', 'identity', 'compliance');
if (q.includes('databas')) tests.push('migration', 'integration', 'rollback');
if (q.includes('auth')) tests.push('integration', 'security', 'session');
if (q.includes('frontend')) tests.push('e2e', 'ui', 'accessibility');
return { type: 'tests', tests: [...new Set(tests)] };
}
reasonDependencies(q) {
const deps = [];
if (q.includes('wallet')) deps.push('stripe', 'database', 'transaction', 'payout');
if (q.includes('mission')) deps.push('claim', 'submission', 'photo', 'user');
if (q.includes('auth')) deps.push('kyc', 'user', 'session', 'token');
if (q.includes('externa')) deps.push('stripe', 'aws', 'sendgrid', 'twilio');
if (q.includes('databas')) deps.push('wallet', 'mission', 'auth', 'kyc', 'user');
return { type: 'dependencies', dependencies: [...new Set(deps)] };
}
reasonRisk(q) {
if (q.includes('kritisk')) {
return { type: 'risk', level: 'high', components: ['wallet', 'auth', 'kyc', 'payment'] };
}
if (q.includes('stripe')) {
return { type: 'risk', level: 'high', factors: ['financial', 'payment_failure'] };
}
if (q.includes('databas')) {
return { type: 'risk', level: 'high', factors: ['data_loss', 'migration'] };
}
if (q.includes('frontend')) {
return { type: 'risk', level: 'low', factors: ['ui', 'reversible'] };
}
return { type: 'risk', level: 'unknown' };
}
reasonArchitecture(q) {
const violations = [];
if (q.includes('ui') && q.includes('databas')) {
violations.push({ rule: 'ui_no_direct_db', allowed: false });
}
if (q.includes('domänlogik') && q.includes('api')) {
violations.push({ rule: 'domain_in_service', allowed: false });
}
if (q.includes('externa') && q.includes('adapter')) {
violations.push({ rule: 'external_via_adapter', allowed: true });
}
if (q.includes('betalning') && q.includes('idempotent')) {
violations.push({ rule: 'payments_idempotent', allowed: true });
}
if (q.includes('kyc') && q.includes('loggas')) {
violations.push({ rule: 'kyc_logged', allowed: true });
}
return { type: 'architecture', violations };
}
reasonBusiness(q) {
const steps = [];
if (q.includes('onboarding')) {
steps.push('kyc', 'wallet', 'identity', 'verification');
}
if (q.includes('mission')) {
steps.push('claim', 'photo', 'submission', 'review', 'payout');
}
if (q.includes('payout') && q.includes('misslyckas')) {
return { type: 'business', action: 'retry', escalation: ['notification', 'support'] };
}
return { type: 'business', steps };
}
reasonRollback(q) {
if (q.includes('databas')) {
return { type: 'rollback', method: 'migration', requires: ['rollback_script', 'backup'] };
}
if (q.includes('api')) {
return { type: 'rollback', method: 'revert', requires: ['feature_flag'] };
}
return { type: 'rollback', method: 'unknown' };
}
/**
* Validera svar mot förväntat resultat
*/
validateResult(result, expected) {
const answer = result.answer;
// Kontrollera om svaret innehåller förväntade komponenter
let matchCount = 0;
for (const exp of expected) {
const answerStr = JSON.stringify(answer).toLowerCase();
if (answerStr.includes(exp.toLowerCase())) {
matchCount++;
}
}
const accuracy = expected.length > 0 ? matchCount / expected.length : 0;
return {
passed: accuracy >= 0.5, // Minst hälften måste matcha
accuracy: Math.round(accuracy * 100) + '%',
expected,
actual: answer,
matchCount,
totalExpected: expected.length
};
}
/**
* Kör hela sviten
*/
runSuite() {
console.log(`🧪 Kör Reasoning Suite: ${this.questions.length} frågor\n`);
const results = [];
let passed = 0;
let failed = 0;
let criticalFailed = 0;
for (const question of this.questions) {
const result = this.runQuestion(question);
const validation = this.validateResult(result, question.expected);
const fullResult = {
...result,
validation,
critical: question.critical,
category: question.category
};
results.push(fullResult);
if (validation.passed) {
passed++;
} else {
failed++;
if (question.critical) criticalFailed++;
}
// Logga resultat
appendFileSync(RESULT_PATH, JSON.stringify(fullResult) + '\n', 'utf8');
// Print
const icon = validation.passed ? '✅' : question.critical ? '🔴' : '⚠️';
console.log(`${icon} ${question.id} [${question.category}]`);
console.log(` ${question.question}`);
console.log(` Accuracy: ${validation.accuracy} (${validation.matchCount}/${validation.totalExpected})`);
if (!validation.passed) {
console.log(` Förväntat: ${question.expected.join(', ')}`);
console.log(` Fick: ${JSON.stringify(result.answer)}`);
}
console.log();
}
// Sammanfattning
const summary = {
timestamp: new Date().toISOString(),
total: this.questions.length,
passed,
failed,
criticalFailed,
passRate: Math.round(passed / this.questions.length * 100) + '%',
criticalPassRate: passed + failed - criticalFailed > 0
? Math.round((passed + failed - criticalFailed) / (passed + failed - criticalFailed) * 100) + '%'
: 'N/A'
};
this.printSummary(summary);
this.saveSummary(summary);
return { summary, results };
}
printSummary(summary) {
console.log('═══════════════════════════════════════════════════════════════');
console.log(' REASONING SUITE RESULTAT');
console.log('═══════════════════════════════════════════════════════════════\n');
console.log(`📊 SAMMANFATTNING\n`);
console.log(` Totalt: ${summary.total}`);
console.log(` Godkända: ${summary.passed}`);
console.log(` Misslyckade: ${summary.failed} ${summary.criticalFailed > 0 ? '🔴' : '⚠️'}`);
console.log(` Kritiska misslyckade: ${summary.criticalFailed}`);
console.log();
console.log(` Pass rate: ${summary.passRate}`);
console.log();
if (summary.criticalFailed > 0) {
console.log('🔴 KRITISKA FEL — kräver omedelbar åtgärd!\n');
} else if (summary.failed > 0) {
console.log('⚠️ Varningar — icke-kritiska fel\n');
} else {
console.log('✅ Alla tester godkända!\n');
}
console.log('═══════════════════════════════════════════════════════════════\n');
}
saveSummary(summary) {
const filename = `/home/bernt/.openclaw/workspace/SIL/reasoning-summary-${new Date().toISOString().split('T')[0]}.json`;
writeFileSync(filename, JSON.stringify(summary, null, 2));
console.log(`💾 Sammanfattning sparad: ${filename}\n`);
}
/**
* Kör regressionstest — jämför med tidigare körning
*/
runRegression() {
console.log('🔄 Kör regressionstest...\n');
const current = this.runSuite();
// Ladda tidigare resultat
const previousResults = this.loadPreviousResults();
if (!previousResults) {
console.log('️ Ingen tidigare körning att jämföra med\n');
return current;
}
// Jämför
const changes = [];
for (const result of current.results) {
const prev = previousResults.find(r => r.questionId === result.questionId);
if (!prev) continue;
const prevPassed = prev.validation?.passed;
const currPassed = result.validation?.passed;
if (prevPassed !== currPassed) {
changes.push({
questionId: result.questionId,
question: result.question,
before: prevPassed ? 'PASS' : 'FAIL',
after: currPassed ? 'PASS' : 'FAIL',
regression: prevPassed && !currPassed // Tidigare OK, nu fel
});
}
}
console.log('📊 REGRESSIONSANALYS\n');
if (changes.length === 0) {
console.log('✅ Inga förändringar — stabilt resultat\n');
} else {
for (const change of changes) {
const icon = change.regression ? '🔴 REGRESSION' : '🟢 FÖRBÄTTRING';
console.log(` ${icon}: ${change.questionId}`);
console.log(` ${change.question}`);
console.log(` Före: ${change.before} → Nu: ${change.after}`);
}
console.log();
if (changes.some(c => c.regression)) {
console.log('🔴 REGRESSION DETEKTERAD!');
console.log(' En tidigare godkänd fråga misslyckas nu.');
console.log(' Detta är ett medvetet beslut, inte en bieffekt.\n');
}
}
return { current, changes };
}
loadPreviousResults() {
if (!existsSync(RESULT_PATH)) return null;
const lines = readFileSync(RESULT_PATH, 'utf8')
.split('\n')
.filter(line => line.trim());
// Hämta föregående körning (senaste N rader)
const questionCount = this.questions.length;
if (lines.length < questionCount) return null;
const previousRun = lines.slice(-questionCount * 2, -questionCount);
return previousRun.map(line => {
try { return JSON.parse(line); } catch { return null; }
}).filter(Boolean);
}
}
// ── Main ──────────────────────────────────────────────────────────────────
const suite = new ReasoningSuite();
const command = process.argv[2] || '--run';
if (command === '--run') {
suite.runSuite();
} else if (command === '--regression') {
suite.runRegression();
} else if (command === '--list') {
console.log(`📋 ${suite.questions.length} standardfrågor:\n`);
for (const q of suite.questions) {
const icon = q.critical ? '🔴' : '🟡';
console.log(` ${icon} [${q.category}] ${q.id}: ${q.question}`);
}
} else {
console.log('Användning:');
console.log(' node reasoning-suite.mjs --run # Kör hela sviten');
console.log(' node reasoning-suite.mjs --regression # Kör regressionstest');
console.log(' node reasoning-suite.mjs --list # Lista alla frågor');
}