147 lines
13 KiB
JavaScript
147 lines
13 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { mkdir, writeFile, realpath, lstat, mkdtemp, open } from 'node:fs/promises'
|
|
import { basename, join, resolve, relative, isAbsolute } from 'node:path'
|
|
import { createRequire } from 'node:module'
|
|
import { pathToFileURL, fileURLToPath } from 'node:url'
|
|
import { randomBytes } from 'node:crypto'
|
|
import { spawn } from 'node:child_process'
|
|
|
|
|
|
const diagnosticLogCap = 1024 * 1024
|
|
const diagnosticNames = new Set(['Error', 'AssertionError', 'McpError', 'TypeError', 'RangeError'])
|
|
const diagnosticCodes = new Set(['ERR_ASSERTION', 'VERIFICATION_FAILED', 'DEVICE_UNAVAILABLE', 'PLAN_EXPIRED', 'RATE_LIMITED', 'VRF_ACK_JOURNAL_UNCONFIRMED', 'VRF_VERIFIED_CHECKPOINT_UNCONFIRMED', '42883', '22P02'])
|
|
/** Pure projection only: no arbitrary log text, SQL, native rows or credentials. */
|
|
export function projectVrfDiagnostics(log, privateValues = [], expectedSlice = null) {
|
|
assert.equal(typeof log, 'string'); assert(Array.isArray(privateValues))
|
|
assert(Buffer.byteLength(log) <= diagnosticLogCap, 'Diagnostic source log exceeded existing cap')
|
|
let redacted = log
|
|
for (const value of [...privateValues, 'owned-vrf-loopback-password'].filter(value => typeof value === 'string' && value).sort((a,b) => b.length-a.length)) redacted = redacted.split(value).join('[REDACTED]')
|
|
redacted = redacted.replace(/postgres(?:ql)?:\/\/[^\s'"]+/gi, '[REDACTED_DATABASE_URL]')
|
|
.replace(/mmcp_[a-z]+_[A-Za-z0-9_-]+/g, '[REDACTED_MCP_TOKEN]')
|
|
.replace(/\beyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, '[REDACTED_JWT]')
|
|
const records = [], acceptedCaseSequence = []; let malformedLines = 0, omittedLines = 0, incrementalRecords = 0, truncatedRecords = 0, droppedCaseLabels = 0, lastAcceptedCase = null, finalComponentPresent = false
|
|
const labelPattern = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$/
|
|
const retainLabel = label => { if (typeof label !== 'string' || !labelPattern.test(label)) { droppedCaseLabels++; return }; if (acceptedCaseSequence.length === 128) { acceptedCaseSequence.shift(); droppedCaseLabels++ }; acceptedCaseSequence.push(label); lastAcceptedCase = label }
|
|
const retain = value => { if (records.length === 128) { records.shift(); truncatedRecords++ } records.push(value) }
|
|
for (const line of redacted.split(/\r?\n/).filter(Boolean)) {
|
|
let value; try { value = JSON.parse(line) } catch {
|
|
malformedLines++
|
|
const name = /^(Error|AssertionError|McpError|TypeError|RangeError)(?:[ :\[]|$)/.exec(line)?.[1]
|
|
if (name) retain({ diagnosticError: true, safeName: name, safeCode: line.includes('[ERR_ASSERTION]') ? 'ERR_ASSERTION' : 'other' })
|
|
const normalized = line.replaceAll('\\', '/'), position = /vrf-production-execution-owned\.(?:ts|mts):(\d{1,7}):(\d{1,7})\)?$/.exec(normalized)
|
|
if (position && (normalized.includes('/backend/src/test/vrf-production-execution-owned.ts:') || normalized.includes('/tmp/mcp-vrf-all68-staged-20261101/vrf-production-execution-owned.mts:'))) retain({ source: 'owned-vrf-fixture', line: Number(position[1]), column: Number(position[2]) })
|
|
continue
|
|
}
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) { omittedLines++; continue }
|
|
const row = {}
|
|
if (value.acceptedCase) { incrementalRecords++; row.acceptedCaseRecord = true; const labels = Array.isArray(value.acceptedCase) ? value.acceptedCase : [value.acceptedCase]; if (labels.length <= 68) for (const label of labels) retainLabel(label); else droppedCaseLabels += labels.length }
|
|
if (Array.isArray(value.accepted)) { row.finalCaseCount = value.accepted.length; if (expectedSlice !== null && value.slice === expectedSlice) finalComponentPresent = true }
|
|
if (['planned','queued','running','applied','failed','cancelled','unknown','expired','rejected','unconfirmed'].includes(value.operationState)) row.operationState = value.operationState
|
|
for (const key of ['phase', 'elapsedMs', 'nativeReads', 'nativeWrites', 'roundTrips', 'lastSqlMs', 'totalSqlMs', 'maxSqlMs']) if (Number.isSafeInteger(value[key]) && value[key] >= 0) row[key] = value[key]
|
|
if (value.diagnosticWaitSql === true) row.diagnosticWaitSql = true
|
|
if (value.diagnosticError === true) { row.diagnosticError = true; row.safeName = diagnosticNames.has(value.safeName) ? value.safeName : 'other'; row.safeCode = diagnosticCodes.has(value.safeCode) ? value.safeCode : 'other' }
|
|
if (!Object.keys(row).length) { omittedLines++; continue }
|
|
retain(row)
|
|
}
|
|
return { version: 1, rawLogRetained: false, sourceCapBytes: diagnosticLogCap, capturedLogBytes: Buffer.byteLength(log), malformedLines, omittedLines, incrementalRecords, truncatedRecords, finalComponentPresent, acceptedCaseSequence, lastAcceptedCase, droppedCaseLabels, records }
|
|
}
|
|
export function vrfSliceDiagnostic(result, log, childBudgetMs, privateValues = []) {
|
|
assert(result && typeof result === 'object')
|
|
assert(['baseline','physical','vlan','disabled','mixed','reconcile','sql-journal','guards','coexistence','alias'].includes(result.slice))
|
|
assert(Number.isSafeInteger(result.code) && typeof result.childExitConfirmed === 'boolean')
|
|
assert(Number.isSafeInteger(result.caseCount) && result.caseCount >= 0)
|
|
assert.equal(childBudgetMs, result.slice === 'baseline' ? 240000 : 120000)
|
|
assert(result.failure === null || ['child-deadline','child-log-limit','child-signal','child-error','child-error-after-pid','child-exit-unconfirmed','missing-component-receipt'].includes(result.failure))
|
|
assert(result.accepted === (result.code === 0 && result.failure === null && result.childExitConfirmed))
|
|
return { ...result, capMs: childBudgetMs, diagnostics: projectVrfDiagnostics(log, privateValues, result.slice), scope: 'same-owned-PG-test-only', actualRouter: false, forwardingVerified: false }
|
|
}
|
|
async function writeDiagnostic(path, value, maxBytes = 65536) {
|
|
const bytes = Buffer.from(JSON.stringify(value, null, 2) + '\n'); assert(bytes.length <= maxBytes)
|
|
const stream = await open(path, 'wx', 0o600)
|
|
try { await stream.writeFile(bytes); await stream.sync() } finally { await stream.close() }
|
|
}
|
|
|
|
/** Existing test runner owns the PG lifecycle. This component creates only
|
|
* bounded Node children on that same synthetic DB, with an empty private cwd.
|
|
* It never reads backend/.env, creates a PG, or accepts an application URL. */
|
|
export async function runOwnedVrfProductionProfile({ directory, database, databaseUrl }) {
|
|
const target = new URL(databaseUrl)
|
|
assert.equal(target.hostname, '127.0.0.1'); assert.equal(target.username, 'mcp_test')
|
|
assert(/^mm_accept_166_\d+$/.test(database)); assert.equal(target.pathname, '/' + database)
|
|
const owned = resolve(directory)
|
|
assert.equal(await realpath(owned), owned); assert(basename(owned).startsWith('mm-mcp-test-'))
|
|
assert(!(await lstat(owned)).isSymbolicLink())
|
|
// Fixed destination derived from this source module, never caller/env controlled.
|
|
const repository = fileURLToPath(new URL('../../', import.meta.url)), reportRoot = join(repository, 'tmp')
|
|
assert.equal(await realpath(reportRoot), reportRoot); assert(!(await lstat(reportRoot)).isSymbolicLink())
|
|
const diagnosticDirectory = await mkdtemp(join(reportRoot, 'mcp-vrf-profile-'))
|
|
assert.equal(await realpath(diagnosticDirectory), diagnosticDirectory); assert(!(await lstat(diagnosticDirectory)).isSymbolicLink())
|
|
assert.equal(relative(reportRoot, diagnosticDirectory), basename(diagnosticDirectory)); assert(basename(diagnosticDirectory).startsWith('mcp-vrf-profile-'))
|
|
const persist = (name, value) => writeDiagnostic(join(diagnosticDirectory, name), value)
|
|
const diagnosticPath = relative(repository, diagnosticDirectory)
|
|
await persist('started.json', { version: 1, status: 'running', accepted: false, childExitConfirmed: false, scope: 'same-owned-PG-test-only' })
|
|
console.log(JSON.stringify({ profile: 'test:vrf:production', diagnosticDirectory: diagnosticPath, status: 'running' }))
|
|
const results = []
|
|
let currentSlice = null, currentChildExitConfirmed = false, lastSliceResult = null
|
|
try {
|
|
const entry = fileURLToPath(new URL('../src/test/vrf-production-execution-owned.ts', import.meta.url))
|
|
const report = fileURLToPath(new URL('../../docs/mcp-chr-vrf-companion-native-v5.json', import.meta.url))
|
|
const loader = pathToFileURL(createRequire(import.meta.url).resolve('tsx')).href
|
|
const safe = Object.fromEntries(['SystemRoot', 'WINDIR', 'TEMP', 'TMP', 'LANG', 'LC_ALL'].filter(key => typeof process.env[key] === 'string').map(key => [key, process.env[key]]))
|
|
for (const slice of ['baseline', 'physical', 'vlan', 'disabled', 'mixed', 'reconcile', 'sql-journal', 'guards', 'coexistence', 'alias']) {
|
|
currentSlice = slice; currentChildExitConfirmed = false; lastSliceResult = null
|
|
await persist(slice + '-started.json', { version: 1, slice, status: 'running', accepted: false, childExitConfirmed: false })
|
|
const childBudgetMs = slice === 'baseline' ? 240000 : 120000
|
|
const cwd = join(owned, 'vrf-production-' + slice), rel = relative(owned, cwd)
|
|
assert(rel && !rel.startsWith('..') && !isAbsolute(rel)); await mkdir(cwd)
|
|
assert.equal(await realpath(cwd), cwd); assert(!(await lstat(cwd)).isSymbolicLink())
|
|
let log = '', failure = null, exited = false, stopTimer, unknownTimer
|
|
const authSecret = randomBytes(32).toString('hex')
|
|
const child = spawn(process.execPath, ['--import', loader, entry], { cwd, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'], env: {
|
|
...safe, NODE_OPTIONS: '', NODE_ENV: 'test', DATABASE_URL: databaseUrl,
|
|
DATABASE_PATH: join(cwd, 'no-legacy.sqlite'), MCP_STATE_DIR: join(cwd, 'state'),
|
|
AUTH_REQUIRED: 'true', AUTH_JWT_SECRET: authSecret, MCP_PUBLIC_URL: 'http://localhost:8000/mcp',
|
|
MMAPP_OWN_VRF_EXECUTION: 'true', MMAPP_VRF_EXECUTION_SLICE: slice, MMAPP_VRF_NATIVE_REPORT: report,
|
|
MMAPP_VRF_SHARED_OWNED_PG: 'true', MMAPP_OWN_SQL_DB: database,
|
|
} })
|
|
const stop = reason => {
|
|
failure ??= reason
|
|
if (exited) return
|
|
child.kill('SIGTERM')
|
|
stopTimer ??= setTimeout(() => { if (!exited) child.kill('SIGKILL') }, 5000)
|
|
}
|
|
const deadline = setTimeout(() => stop('child-deadline'), childBudgetMs)
|
|
for (const stream of [child.stdout, child.stderr]) stream.on('data', bytes => {
|
|
if (Buffer.byteLength(log) + bytes.length > 1024 * 1024) { stop('child-log-limit'); return }
|
|
log += bytes.toString()
|
|
})
|
|
const code = await new Promise(resolveExit => {
|
|
child.once('exit', (status, signal) => { exited = true; if (signal) failure ??= 'child-signal'; resolveExit(status ?? 1) })
|
|
child.once('error', () => { failure ??= 'child-error'; if (!child.pid) { exited = true; resolveExit(1) } else stop('child-error-after-pid') })
|
|
unknownTimer = setTimeout(() => { if (!exited) { stop('child-exit-unconfirmed'); resolveExit(1) } }, childBudgetMs + 15000)
|
|
})
|
|
clearTimeout(deadline); clearTimeout(stopTimer); clearTimeout(unknownTimer)
|
|
const expectedCases = { baseline: 11, physical: 7, vlan: 9, disabled: 9, mixed: 7, reconcile: 6, 'sql-journal': 1, guards: 11, coexistence: 3, alias: 4 }
|
|
const component = log.trim().split(/\r?\n/).flatMap(line => { try { return [JSON.parse(line)] } catch { return [] } }).findLast(value => value.slice === slice && Array.isArray(value.accepted))
|
|
if (!component || component.accepted.length !== expectedCases[slice] || component.actualSql !== true || component.actualCrypto !== true || component.actualAuthPolicyApprovalRuntime !== true || component.fakeVerifier !== false || component.actualRouter !== false || component.forwardingVerified !== false) failure ??= 'missing-component-receipt'
|
|
const result = { slice, accepted: code === 0 && !failure && exited, caseCount: component?.accepted?.length ?? 0, code, failure, childPid: child.pid ?? null, childExitConfirmed: exited }
|
|
lastSliceResult = result; currentChildExitConfirmed = exited
|
|
const diagnostic = vrfSliceDiagnostic(result, log, childBudgetMs, [databaseUrl, target.password, decodeURIComponent(target.password), authSecret])
|
|
await persist(slice + '-result.json', diagnostic)
|
|
// Persist outside the owner's cleanup boundary BEFORE any next slice/return.
|
|
await writeFile(join(cwd, 'fixture.log'), JSON.stringify(diagnostic.diagnostics, null, 2))
|
|
await writeFile(join(cwd, 'acceptance.json'), JSON.stringify(result, null, 2))
|
|
results.push(result)
|
|
console.log(JSON.stringify({ profile: 'test:vrf:production', ...result }))
|
|
if (!result.accepted) break
|
|
}
|
|
const receipt = { accepted: results.length === 10 && results.every(result => result.accepted), childExitConfirmed: results.every(result => result.childExitConfirmed), sameOwnedPg: true, actualRouter: false, forwardingVerified: false, results }
|
|
await persist('terminal.json', { version: 1, status: 'terminal', ...receipt, requiredCases: 68, completedCases: results.reduce((sum, value) => sum + value.caseCount, 0), diagnosticDirectory: diagnosticPath })
|
|
await writeFile(join(owned, 'vrf-production-acceptance.json'), JSON.stringify(receipt, null, 2))
|
|
return receipt
|
|
} catch {
|
|
await persist('wrapper-failure.json', { version: 1, status: 'refused', accepted: false, phase: currentSlice, failure: 'wrapper-exception', childExitConfirmed: currentChildExitConfirmed && results.every(value => value.childExitConfirmed), completedSlices: results.length, lastSliceResult, results })
|
|
throw Error('VRF profile refused; bounded diagnostics retained at ' + diagnosticPath)
|
|
}
|
|
}
|