import assert from 'node:assert/strict'; import Problems from '../../system-problems.js'; import { mkdirSync, readFileSync, writeFileSync, existsSync, renameSync, unlinkSync, statSync } from 'node:fs'; // Only called inside ui-test's private namespace. The real Resources.qml runs; // journal/systemctl and terminal dispatch receive synthetic boundary doubles. export async function runResourceProbe({ original, env, start, stop, waitFor, ipc, screenshot, snapshot, setFixture }) { assert.equal(process.env.NUGULAND_UI_SANDBOX, '1'); const prior = readFileSync('/project/Resources.qml', 'utf8'); const promptPath = '/project/prompts/system-repair.md'; const originalPrompt = readFileSync(promptPath, 'utf8'); const bin = '/tmp/resource-probe-bin'; const stateHome = "/home/test/state '$(literal)"; const repairDirectory = `${stateHome}/nuguland/repairs`; mkdirSync(bin); writeFileSync('/project/Resources.qml', original); writeFileSync('/project/resource-probe.qml', readFileSync('/project/test-fixtures/ui/resource-probe.qml', 'utf8').replace('"../.."', '"."')); const boot = 'a'.repeat(32); const timestamp = Date.now() - 120000; const records = [ {__CURSOR: 's=crash', _BOOT_ID: boot, __REALTIME_TIMESTAMP: String(timestamp * 1000), MESSAGE_ID: 'fc2e22bc6ee647b6b90729ab34a250b1', _SYSTEMD_UNIT: 'systemd-coredump@0-42-0.service', COREDUMP_COMM: 'synthetic editor', COREDUMP_EXE: '/usr/bin/editor', COREDUMP_UID: '1000', COREDUMP_PID: '123', COREDUMP_SIGNAL_NAME: 'SIGSEGV'}, {__CURSOR: 's=oom', _BOOT_ID: boot, __REALTIME_TIMESTAMP: String((timestamp - 60000) * 1000), _TRANSPORT: 'kernel', MESSAGE: 'Out of memory: Killed process 456 (worker) total-vm:1234kB'} ]; records.push( {...records[0], __CURSOR: 's=earlier', __REALTIME_TIMESTAMP: String((timestamp - 60000) * 1000), COREDUMP_PID: '100', COREDUMP_SIGNAL_NAME: 'SIGABRT'}, {...records[0], __CURSOR: 's=oldest', __REALTIME_TIMESTAMP: String((timestamp - 120000) * 1000), COREDUMP_PID: '90'}, {...records[0], __CURSOR: 's=other-app', COREDUMP_EXE: '/opt/unrelated-editor'} ); const journalText = records.map(row => JSON.stringify(row)).join('\n'); writeFileSync('/tmp/resource-probe-journal', journalText); writeFileSync('/tmp/resource-probe-mode', 'records'); const programs = { journalctl: `const mode = fs.readFileSync('/tmp/resource-probe-mode', 'utf8'); if (mode === 'denied') { console.error('journal access denied'); process.exit(1); } if (mode === 'malformed') { console.log('{broken'); process.exit(0); } if (mode === 'empty') process.exit(1); console.log(fs.readFileSync('/tmp/resource-probe-journal', 'utf8'));`, systemctl: `console.log('same.service loaded failed failed synthetic service');`, checkupdates: `console.log('fixture-package 1 -> 2');`, pi: `process.exit(0);`, wezterm: `fs.appendFileSync('/evidence/resource-dispatch.jsonl', JSON.stringify(process.argv.slice(2)) + '\\n');` }; for (const [name, body] of Object.entries(programs)) { writeFileSync(`${bin}/${name}`, `#!/usr/bin/node\nconst fs = require('node:fs');\n${body}\n`, {mode: 0o700}); } const child = start('qs', ['--no-color', '-p', '/project/resource-probe.qml'], 'resource-probe', {...env, PATH: `${bin}:/usr/bin`, XDG_STATE_HOME: stateHome}); setFixture('/project/resource-probe.qml', '/evidence/resource-probe.log', child); const checks = []; async function check(label, predicate) { const state = await waitFor(label, () => { const s = snapshot(); return predicate(s) ? s : null; }); checks.push(label); console.log(`pass: ${label}`); return state; } function promptFromArgs(args) { assert(args.at(-1).startsWith('@' + repairDirectory + '/.request-')); const path = args.at(-1).slice(1); assert.equal(statSync(path).mode & 0o777, 0o600, 'private prompt snapshot'); return readFileSync(path, 'utf8'); } { await waitFor('production resource probe loaded', () => readFileSync('/evidence/resource-probe.log', 'utf8').includes('Configuration Loaded')); const state = await check('production journal groups repeated crashes but separates unrelated apps and oom', s => s.incidentStatus === 'ready' && s.issues.filter(issue => issue.context).length === 3 && s.issues.some(issue => issue.id === 'updates')); const group = state.issues.find(issue => issue.occurrences === 3); assert(group); const groupId = group.id; const groupEvents = Problems.groupIncidents(Problems.parseJournal(journalText).issues).find(issue => issue.id === groupId).events; assert.deepEqual(groupEvents.map(event => event.cursor), ['s=crash', 's=earlier', 's=oldest']); ipc('act', 'updates'); await check('production dispatch rejects an existing update issue', s => !s.pending && s.dispatches === 0); assert(!existsSync('/evidence/resource-dispatch.jsonl')); const failed = state.issues.find(issue => issue.id === 'failed-services'); assert.deepEqual(failed.action.scopes, [{scope: 'system', units: ['same.service']}, {scope: 'user', units: ['same.service']}]); writeFileSync(promptPath, 'template without a context slot'); ipc('act', 'failed-services'); await check('invalid prompt template blocks dispatch', s => !s.pending && s.agentError === 'repair prompt unavailable or invalid' && s.dispatches === 0); writeFileSync(promptPath, originalPrompt); // No executable in session PATH: visible failure, no detached child. renameSync(`${bin}/pi`, `${bin}/pi-unavailable`); ipc('act', 'failed-services'); await check('production launch preflight rejects missing pi', s => !s.pending && s.agentError.length > 0 && s.dispatches === 0); assert(!existsSync('/evidence/resource-dispatch.jsonl')); renameSync(`${bin}/pi-unavailable`, `${bin}/pi`); mkdirSync(`${stateHome}/nuguland`, {recursive: true}); writeFileSync(repairDirectory, 'owned fixture obstruction'); ipc('act', 'failed-services'); await check('unusable repair directory blocks dispatch with visible feedback', s => !s.pending && s.agentError === 'repair directory unavailable or not writable' && s.dispatches === 0); assert.equal(readFileSync(repairDirectory, 'utf8'), 'owned fixture obstruction'); assert(!existsSync('/evidence/resource-dispatch.jsonl')); await screenshot('system-repair-directory-unavailable'); unlinkSync(repairDirectory); ipc('act', 'failed-services'); await check('production retry dispatches one detached terminal request', s => !s.pending && s.dispatches === 1); await waitFor('terminal boundary received argv', () => existsSync('/evidence/resource-dispatch.jsonl')); const args = JSON.parse(readFileSync('/evidence/resource-dispatch.jsonl', 'utf8').trim()); assert.deepEqual(args.slice(0, -1), ['start', '--cwd', repairDirectory, '--', `${bin}/pi`, '--name', 'arrrgh: 2 failed services', '--']); const initialPrompt = promptFromArgs(args); const snapshotData = JSON.parse(initialPrompt.split('## Diagnostic context\n\n')[1]); assert.deepEqual(snapshotData.serviceScopes, failed.action.scopes); assert.equal(snapshotData.machine.host, 'synthetic-probe'); assert(initialPrompt.includes('Privileged, destructive')); assert(initialPrompt.includes('Never mark resolved without verification')); assert(initialPrompt.includes('one Markdown report per issue')); assert(initialPrompt.includes('Choose your own diagnostic tools')); assert(!('suggestedReadOnlyCommands' in snapshotData)); assert(statSync(repairDirectory).isDirectory()); assert.equal(statSync(repairDirectory).mode & 0o777, 0o700); mkdirSync(`${repairDirectory}/issues`, {mode: 0o700}); writeFileSync(`${repairDirectory}/issues/existing.md`, 'existing verified report', {mode: 0o600}); renameSync(promptPath, `${promptPath}.unavailable`); ipc('act', groupId); await check('missing prompt refuses cached template after prior success', s => !s.pending && s.agentError === 'repair prompt unavailable or invalid' && s.dispatches === 1); renameSync(`${promptPath}.unavailable`, promptPath); writeFileSync(promptPath, '\n' + originalPrompt); ipc('act', groupId); await check('production historical action dispatches the selected crash group', s => s.dispatches === 2 && s.action.id === groupId); await waitFor('second terminal request captured', () => readFileSync('/evidence/resource-dispatch.jsonl', 'utf8').trim().split('\n').length === 2); const crashArgs = JSON.parse(readFileSync('/evidence/resource-dispatch.jsonl', 'utf8').trim().split('\n')[1]); assert.equal(crashArgs[2], repairDirectory, 'all issues share the stable repair cwd'); assert.equal(readFileSync(`${repairDirectory}/issues/existing.md`, 'utf8'), 'existing verified report'); const crashPrompt = promptFromArgs(crashArgs); assert(crashPrompt.startsWith(''), 'template changes apply to the next launch without restart'); const crashData = JSON.parse(crashPrompt.split('## Diagnostic context\n\n')[1]); assert.deepEqual(crashData.issue.events, groupEvents, 'all selected occurrences, no unrelated application'); assert.equal(crashData.issue.occurrences, 3); assert.equal(promptFromArgs(args), initialPrompt, 'later launches preserve earlier request evidence'); assert.equal(crashData.issue.context.bootId, boot); assert.equal(crashData.issue.context.cursor, 's=crash'); assert.equal(crashData.issue.context.metadata.comm, 'synthetic editor'); assert.strictEqual(crashData.issue.historical, true); assert.strictEqual(crashData.issue.type, 'crash'); assert(!('suggestedReadOnlyCommands' in crashData)); const many = Array.from({length: 100}, (_, i) => ({...records[0], __CURSOR: 's=large-' + i + ';' + 'x'.repeat(480), __REALTIME_TIMESTAMP: String((timestamp + i * 1000) * 1000), COREDUMP_COMM: 'editor ' + '界'.repeat(140), COREDUMP_PID: String(i + 1), COREDUMP_UNIT: 'unit-' + 'x'.repeat(150), COREDUMP_USER_UNIT: 'user-unit-' + 'y'.repeat(150) })); writeFileSync('/tmp/resource-probe-journal', many.map(row => JSON.stringify(row)).join('\n')); await check('production group retains the entire 100-record window with stable identity', s => s.issues.some(issue => issue.id === groupId && issue.occurrences === 100)); writeFileSync(`${bin}/mktemp`, '#!/bin/sh\nexit 1\n', {mode: 0o700}); ipc('act', groupId); await check('snapshot write failure blocks dispatch without dropping occurrences', s => !s.pending && s.dispatches === 2 && s.agentError === 'repair context could not be saved'); unlinkSync(`${bin}/mktemp`); ipc('act', groupId); await check('large grouped prompt retries through file transport', s => !s.pending && s.dispatches === 3 && s.action.id === groupId); await waitFor('large terminal request captured', () => readFileSync('/evidence/resource-dispatch.jsonl', 'utf8').trim().split('\n').length === 3); const largeArgs = JSON.parse(readFileSync('/evidence/resource-dispatch.jsonl', 'utf8').trim().split('\n')[2]); const largePrompt = promptFromArgs(largeArgs); assert(Buffer.byteLength(largePrompt, 'utf8') > 131072, 'beyond Linux per-argument size limit'); assert(largeArgs.every(arg => Buffer.byteLength(arg, 'utf8') < 131072)); const largeData = JSON.parse(largePrompt.split('## Diagnostic context\n\n')[1]); const expectedLarge = Problems.groupIncidents(Problems.parseJournal(many.map(row => JSON.stringify(row)).join('\n')).issues)[0]; assert.deepEqual(largeData.issue.events, expectedLarge.events, 'every byte survives stdin and file transport'); writeFileSync('/tmp/resource-probe-journal', journalText); await check('shorter journal window recomputes crash groups without hidden state', s => s.issues.filter(issue => issue.context).length === 3 && s.issues.some(issue => issue.id === groupId && issue.occurrences === 3)); await screenshot('system-grouped-production'); writeFileSync('/tmp/resource-probe-mode', 'denied'); await check('journal permission failure retains last-good incidents', s => s.incidentStatus !== 'ready' && s.issues.filter(issue => issue.context).length === 3); writeFileSync('/tmp/resource-probe-mode', 'records'); await check('journal availability recovers without reload', s => s.incidentStatus === 'ready'); writeFileSync('/tmp/resource-probe-mode', 'malformed'); await check('malformed journal data retains last-good incidents', s => s.incidentStatus !== 'ready' && s.issues.filter(issue => issue.context).length === 3); writeFileSync('/tmp/resource-probe-mode', 'empty'); await check('successful empty journal refresh clears stale history', s => s.incidentStatus === 'ready' && !s.issues.some(issue => issue.context)); const notificationsBeforeScreenshot = snapshot().notifications; await screenshot('system-production-boundaries'); await check('screenshot notification completes before probe teardown', s => s.notifications > notificationsBeforeScreenshot); writeFileSync('/evidence/resource-probe-checks.json', JSON.stringify(checks, null, 2)); } await stop(child); writeFileSync(promptPath, originalPrompt); writeFileSync('/project/Resources.qml', prior); }