Files
codebase-memory-mcp/graph-ui/tools/pr-2068-errors-e2e.mjs
T
Bernhard Jackiewicz 4372d9fada feat(ui): integrate repository evidence and change analysis on one daemon
Add source-backed repository navigation, selection context and a shared change
analysis workspace for symbols, working-tree changes and Git comparisons.
Persist agent events and project logs through the existing daemon with SQLite
schema 4, bounded polling, retention and restart-safe deduplication.

Repair compact RPC decoding, path-search races, source pagination and identity,
large map responses, live error propagation and explicit local diagnosis flows.
Include the real-repository browser runners and focused regression coverage.

Validation: 2473 frontend tests, 203 acceptance checks, production build,
style/promise gates, 5 reference-boundary checks and 7 local hook tests pass.
The complete repository-map browser flow also passes after daemon restart.
Global C lint and cross-platform release validation remain open and are
recorded in the accompanying handoff report.

Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
2026-09-10 00:08:19 +02:00

208 lines
16 KiB
JavaScript

#!/usr/bin/env node
// Reproducible, explicitly synthetic repository fixture; real daemon and indexer.
// Requires the existing isolated daemon at 127.0.0.1:9749. No server is started.
import assert from 'node:assert/strict';
import { mkdir, writeFile, readFile, readdir, chmod } from 'node:fs/promises';
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { dirname, resolve, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { chromium } from 'playwright';
const UI_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const OUT = join(UI_ROOT, 'verification/pr-2068');
const ORIGIN = 'http://127.0.0.1:9749';
const CACHE = '/tmp/cbm-2068-cache';
const FIXTURE = '/tmp/cbm-2068-error-fixture';
const PROJECT = 'pr2068-error-fixture';
const proofPrefix = process.env.CBM_VERIFICATION_PREFIX ?? '';
if (!/^[a-z0-9-]*$/.test(proofPrefix)) throw new Error('Invalid local proof prefix');
const marker = 'PR 2068 EXPLICIT TEST FIXTURE - synthetic files for local error verification\n';
const report = { startedAt: new Date().toISOString(), origin: ORIGIN, fixture: FIXTURE, project: PROJECT, syntheticFixture: true,
actions: [], directRequests: [], browserRequests: [], blockedExternalRequests: [], screenshots: [], checks: {}, cleanup: [] };
let browser;
let ownsFixture = false;
let rpcId = 0;
const delay = (ms) => new Promise((done) => setTimeout(done, ms));
const saveReport = () => writeFile(join(OUT, `${proofPrefix}errors-e2e.json`), JSON.stringify(report, null, 2) + '\n');
async function json(path, body) {
const response = await fetch(ORIGIN + path, { method: body ? 'POST' : 'GET', ...(body ? { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) } : {}) });
const text = await response.text();
let payload;
try { payload = JSON.parse(text); } catch { payload = { raw: text }; }
report.directRequests.push({ at: new Date().toISOString(), method: body ? 'POST' : 'GET', path, status: response.status });
if (!response.ok) throw new Error(`${path}: HTTP ${response.status}: ${text.slice(0, 300)}`);
return payload;
}
async function rpc(name, args) {
const response = await json('/rpc', { jsonrpc: '2.0', id: ++rpcId, method: 'tools/call', params: { name, arguments: { ...args, format: 'json' } } });
if (response.error || response.result?.isError) throw new Error(JSON.stringify(response));
return JSON.parse(response.result.content[0].text);
}
async function index(project) {
const begin = await json('/api/index', { root_path: FIXTURE, project_name: project });
report.actions.push({ action: 'POST /api/index', project, root: FIXTURE, acknowledgement: begin });
const deadline = Date.now() + 180000;
while (Date.now() < deadline) {
const jobs = await json('/api/index-status');
const job = jobs.find((item) => item.slot === begin.slot);
if (job && job.status !== 'indexing') {
report.actions.push({ action: 'Index terminal state', project, job });
return job;
}
await delay(500);
}
throw new Error(`Index job timed out: ${project}`);
}
async function capture(page, name) {
name = proofPrefix + name;
await page.screenshot({ path: join(OUT, name), fullPage: false });
report.screenshots.push({ file: name, url: page.url(), at: new Date().toISOString(), sha256: createHash('sha256').update(await readFile(join(OUT, name))).digest('hex') });
}
async function fixturePrepare() {
await mkdir(FIXTURE, { recursive: true });
const old = await readFile(join(FIXTURE, 'TEST-FIXTURE.txt'), 'utf8').catch((error) => { if (error.code === 'ENOENT') return null; throw error; });
if (old !== null && old !== marker) throw new Error('Refusing to modify an unrecognized existing fixture directory');
if (old === null && (await readdir(FIXTURE)).length > 0) throw new Error('Refusing to overwrite files in an unmarked existing directory');
await writeFile(join(FIXTURE, 'TEST-FIXTURE.txt'), marker);
ownsFixture = true;
await writeFile(join(FIXTURE, 'readable.c'), '/* EXPLICIT TEST FIXTURE */\nint fixture_readable(void) { return 42; }\n');
await writeFile(join(FIXTURE, 'partial.c'), '/* EXPLICIT TEST FIXTURE: malformed declaration */\nint fixture_broken( {\n');
await writeFile(join(FIXTURE, 'excluded.c'), '/* EXPLICIT TEST FIXTURE intentionally excluded */\nint excluded(void) { return 0; }\n');
await writeFile(join(FIXTURE, '.cbmignore'), 'excluded.c\n');
const unreadable = join(FIXTURE, 'unreadable.c');
await chmod(unreadable, 0o600).catch((error) => { if (error.code !== 'ENOENT') throw error; });
await writeFile(unreadable, '/* EXPLICIT TEST FIXTURE: chmod 000 during this run */\nint inaccessible(void) { return 1; }\n');
report.actions.push({ action: 'Created synthetic C fixtures initially readable, including malformed partial.c and excluded.c', path: FIXTURE });
}
async function failedJobWhileWatching(page, workspace, project) {
await page.bringToFront();
await page.locator(`[data-workspace-tab="${workspace}"]`).click();
await page.locator('.daemon-alerts-heading > span').filter({ hasText: 'Last checked' }).waitFor({ timeout: 20000 });
const before = await json('/api/logs?lines=500&min_level=error');
const job = await index(project);
assert.equal(job.status, 'error', 'Unreadable fixture input must fail a real daemon index job');
let event;
const deadline = Date.now() + 20000;
while (Date.now() < deadline) {
const logs = await json('/api/logs?lines=500&min_level=error');
event = logs.records?.find((entry) => entry.id > (before.cursor ?? 0) && entry.message.includes('ui.index.done') && entry.message.includes(FIXTURE));
if (event) break;
await delay(250);
}
assert.ok(event, 'A real failed index completion must reach persisted daemon errors');
const exact = page.locator('.daemon-alert-error').filter({ hasText: `Event #${event.id}` });
await exact.waitFor({ state: 'visible', timeout: 20000 });
assert.ok((await exact.innerText()).includes('ERROR'));
report.checks[`${workspace}LiveError`] = { event, observedWithoutPageReload: true, text: await exact.innerText(), priorityPanelBounds: await page.locator('[data-testid="daemon-alerts"]').boundingBox() };
if (workspace === 'system') {
await page.locator('#system-tab-indexes').click();
await page.locator('.system-job-error').first().waitFor({ timeout: 15000 });
await page.locator('#system-tab-logs').click();
await page.getByLabel('Log severity', { exact: true }).selectOption('error');
await page.locator('.system-log-record.system-log-error').first().waitFor({ timeout: 15000 });
const daemonErrors = await json('/api/logs?lines=200&min_level=error');
await page.locator('.system-log-meta').filter({ hasText: `${daemonErrors.total} matching retained events` }).waitFor({ timeout: 15000 });
report.checks.systemFiltersBeforeLimit = { total: daemonErrors.total, visibleErrorRows: await page.locator('.system-log-record.system-log-error').count(), text: await page.locator('.system-log-meta').innerText() };
assert.ok(daemonErrors.total > 0, 'Retained historical errors cannot disappear behind newer info logs');
await page.getByLabel('Log scope', { exact: true }).selectOption('project');
const scopedErrors = await json(`/api/logs?lines=200&min_level=error&project=${encodeURIComponent(project)}`);
await page.locator('.system-log-meta').filter({ hasText: `${scopedErrors.total} matching retained events` }).waitFor({ timeout: 15000 });
await page.locator('.system-log-record.system-log-error').filter({ hasText: `Event #${event.id}` }).waitFor({ timeout: 15000 });
report.checks.systemProjectScope = { total: scopedErrors.total, project, text: await page.locator('.system-log-meta').innerText() };
await page.getByPlaceholder('Find a source, path or message…').fill('semantic_manifest.err');
const searched = await json(`/api/logs?lines=200&min_level=error&project=${encodeURIComponent(project)}&q=semantic_manifest.err`);
await page.locator('.system-log-meta').filter({ hasText: `${searched.total} matching retained events` }).waitFor({ timeout: 15000 });
report.checks.systemSearchBeforeLimit = { total: searched.total, query: searched.query, text: await page.locator('.system-log-meta').innerText() };
assert.ok(searched.total > 0);
await capture(page, 'errors-system-filtered-history.png');
await page.getByPlaceholder('Find a source, path or message…').fill('');
}
await capture(page, `errors-after-${workspace}.png`);
}
async function main() {
await mkdir(OUT, { recursive: true });
await fixturePrepare();
report.indexWithCoverage = await index(PROJECT);
assert.equal(report.indexWithCoverage.status, 'done', 'Initial readable fixture must establish a real partial index');
report.indexStatus = await rpc('index_status', { project: PROJECT });
report.coverage = await rpc('check_index_coverage', { project: PROJECT, scopes: ['.'], paths: ['unreadable.c', 'partial.c', 'excluded.c'] });
report.checks.coverageContainsPartial = JSON.stringify(report.coverage).includes('parse_partial');
report.checks.coverageContainsExclusion = JSON.stringify(report.coverage).includes('cbmignore');
assert.ok(report.checks.coverageContainsPartial, 'Malformed fixture must produce actual parse_partial coverage');
assert.ok(report.checks.coverageContainsExclusion, '.cbmignore fixture must produce actual exclusion evidence');
await chmod(join(FIXTURE, 'unreadable.c'), 0);
await assert.rejects(readFile(join(FIXTURE, 'unreadable.c')), { code: 'EACCES' });
report.actions.push({ action: 'Set unreadable.c chmod 000; real EACCES verified; next reindex must fail', path: FIXTURE });
browser = await chromium.launch({ headless: true, args: ['--disable-background-networking', '--disable-component-update', '--disable-sync', '--no-first-run', '--use-angle=swiftshader'] });
const context = await browser.newContext({ viewport: { width: 1560, height: 1100 }, reducedMotion: 'reduce' });
await context.route('**/*', async (route) => {
const req = route.request(); const url = req.url();
report.browserRequests.push({ at: new Date().toISOString(), method: req.method(), url, ...(url === `${ORIGIN}/rpc` ? { tool: req.postDataJSON()?.params?.name } : {}) });
if (url.startsWith(ORIGIN + '/') || url.startsWith('data:') || url.startsWith('blob:')) return route.continue();
report.blockedExternalRequests.push(url); return route.abort();
});
const page = await context.newPage();
page.on('pageerror', (error) => { (report.browserErrors ??= []).push(error.message); });
await page.goto(`${ORIGIN}/?project=${PROJECT}`, { waitUntil: 'domcontentloaded' });
await page.locator('[data-testid="daemon-alerts"]').waitFor({ timeout: 60000 });
if (await page.locator('.cbm-welcome-backdrop').isVisible()) {
await page.locator('.cbm-welcome .cbm-primary').click();
report.actions.push({ action: 'Closed first-visit welcome via its normal start button' });
}
await failedJobWhileWatching(page, 'system', PROJECT);
await failedJobWhileWatching(page, 'galaxy', PROJECT);
report.coverageAfterFailedRefresh = await rpc('check_index_coverage', { project: PROJECT, scopes: ['.'], paths: ['unreadable.c', 'partial.c', 'excluded.c'] });
const beforeDiagnosis = report.browserRequests.length;
await page.getByRole('button', { name: 'Inspect index coverage', exact: true }).click();
const panel = page.getByRole('dialog', { name: 'Index coverage diagnosis', exact: true });
await panel.waitFor();
assert.equal(await panel.locator('textarea').count(), 0, 'Opening diagnosis must not generate a report');
report.checks.beforeExplicitDiagnosis = { reportAbsent: true, requests: report.browserRequests.slice(beforeDiagnosis) };
const diagnosisStart = report.browserRequests.length;
await panel.getByRole('button', { name: 'Run local diagnosis', exact: true }).click();
await panel.getByRole('textbox', { name: 'Editable local diagnostic report' }).waitFor({ timeout: 30000 });
report.localReport = await panel.getByRole('textbox', { name: 'Editable local diagnostic report' }).inputValue();
assert.ok(report.localReport.includes('Source lines: 2-2'), 'Actual parser source range must survive the shared coverage join and local report');
report.checks.localReportPreservesParserSourceRanges = true;
report.checks.localDiagnosis = { text: await panel.innerText(), requests: report.browserRequests.slice(diagnosisStart) };
await capture(page, 'errors-local-diagnosis.png');
await panel.getByRole('textbox', { name: 'Editable local diagnostic report' }).scrollIntoViewIfNeeded();
await capture(page, 'errors-local-report.png');
const draft = 'EXPLICIT TEST FIXTURE: locally reviewed and redacted draft. No publication requested.';
await panel.getByRole('textbox', { name: 'Editable local diagnostic report' }).fill(draft);
const downloadPromise = page.waitForEvent('download');
await panel.getByRole('button', { name: 'Download edited report', exact: true }).click();
const download = await downloadPromise;
const draftPath = join(OUT, `${proofPrefix}errors-reviewed-local-report.txt`); await download.saveAs(draftPath);
assert.equal(await readFile(draftPath, 'utf8'), draft);
report.checks.downloadContainsOnlyEditedText = true;
report.checks.onlySameDaemonOrigin = report.browserRequests.filter((entry) => !entry.url.startsWith('blob:') && !entry.url.startsWith('data:')).every((entry) => new URL(entry.url).origin === ORIGIN);
report.sqliteEvidence = JSON.parse(execFileSync('python3', ['-c', `import sqlite3,json\na=sqlite3.connect('file:${CACHE}/activity.db?mode=ro',uri=True)\ns=sqlite3.connect('file:${CACHE}/${PROJECT}.db?mode=ro',uri=True)\nprint(json.dumps({'logs':a.execute("SELECT id,ts,level,source,message FROM daemon_logs WHERE message LIKE '%cbm-2068-error-fixture%' ORDER BY id").fetchall(),'coverage':s.execute("SELECT project,rel_path,kind,detail FROM index_coverage WHERE project=?",('${PROJECT}',)).fetchall()}))`], { encoding: 'utf8' }));
const cutoff = report.checks.systemLiveError.event.id;
report.checks.filterCountsVerifiedInSqlite = JSON.parse(execFileSync('python3', ['-c', `import sqlite3,json,sys
conn=sqlite3.connect('file:${CACHE}/activity.db?mode=ro',uri=True)
cutoff=int(sys.argv[1]); project=sys.argv[2]
print(json.dumps({'as_of_event_id':cutoff,'daemon_errors':conn.execute('SELECT count(*) FROM daemon_logs WHERE level>=3 AND id<=?',(cutoff,)).fetchone()[0],'project_errors':conn.execute('SELECT count(*) FROM daemon_logs WHERE level>=3 AND id<=? AND project=?',(cutoff,project)).fetchone()[0],'project_manifest_errors':conn.execute("SELECT count(*) FROM daemon_logs WHERE level>=3 AND id<=? AND project=? AND instr(lower(message),'semantic_manifest.err')>0",(cutoff,project)).fetchone()[0]}))`, String(cutoff), PROJECT], { encoding: 'utf8' }));
assert.equal(report.checks.filterCountsVerifiedInSqlite.daemon_errors, report.checks.systemFiltersBeforeLimit.total);
assert.equal(report.checks.filterCountsVerifiedInSqlite.project_errors, report.checks.systemProjectScope.total);
assert.equal(report.checks.filterCountsVerifiedInSqlite.project_manifest_errors, report.checks.systemSearchBeforeLimit.total);
report.completedAt = new Date().toISOString();
}
try { await main(); }
catch (error) { report.failure = error.stack ?? String(error); process.exitCode = 1; }
finally {
if (ownsFixture) await chmod(join(FIXTURE, 'unreadable.c'), 0o600).then(() => report.cleanup.push('unreadable.c restored to 0600'), () => {});
if (browser) await browser.close();
await saveReport();
console.log(JSON.stringify({ report: join(OUT, `${proofPrefix}errors-e2e.json`), completed: !!report.completedAt, failure: report.failure, screenshots: report.screenshots, cleanup: report.cleanup }, null, 2));
}