Clarify Gitea verification failure diagnostics
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
c414a03a19
commit
0681529e8e
6 files changed
+126
-25
No files matched your search
@@ -0,0 +1,40 @@
|
||||
// Keep acceptance diagnostics useful without copying request or assertion data.
|
||||
const errorNames = new Set(['Error', 'TypeError', 'AbortError', 'TimeoutError']);
|
||||
const networkCodes = new Set([
|
||||
'ABORT_ERR', 'ECONNREFUSED', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT',
|
||||
'ENOTFOUND', 'EAI_AGAIN', 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_HEADERS_TIMEOUT',
|
||||
'UND_ERR_BODY_TIMEOUT', 'UND_ERR_SOCKET',
|
||||
]);
|
||||
|
||||
export function requestIdentity(method, path) {
|
||||
const pathname = typeof path === 'string' ? path.split('?')[0] : '';
|
||||
return {
|
||||
method: method === 'POST' ? 'POST' : 'GET',
|
||||
path: /^\/api\/[a-zA-Z0-9_/-]+$/.test(pathname) ? pathname : '[invalid path]',
|
||||
};
|
||||
}
|
||||
|
||||
export function requestFailure(request, error) {
|
||||
const name = errorNames.has(error?.name) ? error.name : 'Error';
|
||||
const candidate = error?.cause?.code ?? error?.code;
|
||||
const causeCode = networkCodes.has(candidate) ? candidate : undefined;
|
||||
return { ...request, kind: 'request-error', name, ...(causeCode ? { causeCode } : {}) };
|
||||
}
|
||||
|
||||
export function failureLocation(error) {
|
||||
const lines = typeof error?.stack === 'string' ? error.stack.split('\n') : [];
|
||||
const matches = lines.flatMap(line => {
|
||||
const match = line.match(/(?:^|\/)tools\/(admin-[a-z0-9-]+-verification\.mjs|verify-admin\.mjs):(\d+):(\d+)(?:\)|$)/);
|
||||
return match ? [`tools/${match[1]}:${match[2]}:${match[3]}`] : [];
|
||||
});
|
||||
return matches.find(location => !location.startsWith('tools/verify-admin.mjs:')) ?? matches[0];
|
||||
}
|
||||
|
||||
export function describeFailure(request, location) {
|
||||
if (!request) return undefined;
|
||||
const prefix = `${request.method} ${request.path}`;
|
||||
const result = request.kind === 'request-error'
|
||||
? `failed before completing a response (${request.name}${request.causeCode ? `, ${request.causeCode}` : ''})`
|
||||
: `last completed with HTTP ${request.status}`;
|
||||
return `${prefix} ${result}${location ? `; ${location}` : ''}`;
|
||||
}
|
||||
+24
-13
@@ -13,6 +13,7 @@ import { verifyParticipantAi } from './admin-seat-ai-verification.mjs';
|
||||
import { verifyRoomArchive } from './admin-archive-verification.mjs';
|
||||
import { verifyRoomClosure } from './admin-closure-verification.mjs';
|
||||
import { verifyRoomRemoval } from './admin-removal-verification.mjs';
|
||||
import { requestIdentity, requestFailure, failureLocation } from './admin-verification-diagnostics.mjs';
|
||||
|
||||
const options = {};
|
||||
for (let n = 2; n < process.argv.length; n += 2) {
|
||||
@@ -33,18 +34,28 @@ const key = (await readFile(options['--key-file'], 'utf8')).trim();
|
||||
const base = url.origin;
|
||||
const report = { origin: base, startedAt: new Date().toISOString(), passed: false, checks: [], room: null };
|
||||
let session1, session2, adminId;
|
||||
const requestFailures = new WeakMap();
|
||||
async function request(path, body, cookie, origin = base, expectedAdminId = adminId) {
|
||||
const response = await fetch(base + path, {
|
||||
method: body === undefined ? 'GET' : 'POST',
|
||||
headers: { ...(body === undefined ? {} : { 'content-type': 'application/json', origin }), ...(cookie ? { cookie } : {}),
|
||||
...(expectedAdminId && (path === '/api/admin/audit' || path.startsWith('/api/admin/audit?') || path === '/api/admin/rooms' && body !== undefined || /^\/api\/admin\/rooms\/[^/]+\/(control|lobby|removal|closure|archive|seat-ai|discussion)$/.test(path)) ? { 'X-Dune-Admin-Id': expectedAdminId } : {}) },
|
||||
...(body === undefined ? {} : { body: JSON.stringify(body) }), signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
report.lastRequest = { path, status: response.status };
|
||||
const raw = await response.text();
|
||||
let data;
|
||||
try { data = JSON.parse(raw); } catch { data = null; }
|
||||
return { status: response.status, data, cookie: response.headers.get('set-cookie')?.split(';')[0], rawCookie: response.headers.get('set-cookie'), cache: response.headers.get('cache-control') };
|
||||
const identity = requestIdentity(body === undefined ? 'GET' : 'POST', path);
|
||||
report.inFlightRequest = identity;
|
||||
try {
|
||||
const response = await fetch(base + path, {
|
||||
method: body === undefined ? 'GET' : 'POST',
|
||||
headers: { ...(body === undefined ? {} : { 'content-type': 'application/json', origin }), ...(cookie ? { cookie } : {}),
|
||||
...(expectedAdminId && (path === '/api/admin/audit' || path.startsWith('/api/admin/audit?') || path === '/api/admin/rooms' && body !== undefined || /^\/api\/admin\/rooms\/[^/]+\/(control|lobby|removal|closure|archive|seat-ai|discussion)$/.test(path)) ? { 'X-Dune-Admin-Id': expectedAdminId } : {}) },
|
||||
...(body === undefined ? {} : { body: JSON.stringify(body) }), signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
report.lastRequest = { ...identity, status: response.status };
|
||||
const raw = await response.text();
|
||||
let data;
|
||||
try { data = JSON.parse(raw); } catch { data = null; }
|
||||
return { status: response.status, data, cookie: response.headers.get('set-cookie')?.split(';')[0], rawCookie: response.headers.get('set-cookie'), cache: response.headers.get('cache-control') };
|
||||
} catch (error) {
|
||||
if (error && typeof error === 'object') requestFailures.set(error, requestFailure(identity, error));
|
||||
throw error;
|
||||
} finally {
|
||||
delete report.inFlightRequest;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const denied = await request('/api/admin/rooms');
|
||||
@@ -115,8 +126,8 @@ try {
|
||||
} catch (error) {
|
||||
// Do not serialize assertion payloads: failed authentication responses might contain credentials.
|
||||
report.failure = 'Administrator HTTP verification failed. Investigate the last completed check.';
|
||||
report.failureRequest ??= report.lastRequest;
|
||||
report.failureLocation = error instanceof Error ? error.stack?.split('\n').find(line => line.trim().startsWith('at ') && line.includes('verify-admin.mjs'))?.trim() : undefined;
|
||||
report.failureRequest = (error && typeof error === 'object' ? requestFailures.get(error) : undefined) ?? report.failureRequest ?? report.lastRequest;
|
||||
report.failureLocation = failureLocation(error);
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
for (const cookie of [session1, session2].filter(Boolean)) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { setTimeout as delay } from 'node:timers/promises';
|
||||
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join as joinPath } from 'node:path';
|
||||
import { describeFailure } from './admin-verification-diagnostics.mjs';
|
||||
|
||||
const image = process.argv[2];
|
||||
if (!image) throw new Error('Usage: node tools/verify-container.mjs IMAGE');
|
||||
@@ -171,12 +172,15 @@ try {
|
||||
}
|
||||
console.log('Container origin, restart, replacement, saved seat and HTTP integration checks passed.');
|
||||
} catch (error) {
|
||||
annotateFailure(String(error?.message ?? error).split('\n')[0]);
|
||||
annotateFailure(stage === 'administrator HTTP acceptance'
|
||||
? 'Administrator HTTP acceptance failed; see sanitized details below.'
|
||||
: String(error?.message ?? error).split('\n')[0]);
|
||||
if (stage === 'administrator HTTP acceptance') {
|
||||
try {
|
||||
const report = JSON.parse(readFileSync(joinPath(adminFiles, 'http/report.json'), 'utf8'));
|
||||
const request = report.failureRequest ?? report.lastRequest;
|
||||
if (request) annotateFailure(`${request.path} returned ${request.status}; ${report.failureLocation ?? 'see the failed acceptance check'}`);
|
||||
const detail = describeFailure(request, report.failureLocation);
|
||||
if (detail) annotateFailure(detail);
|
||||
} catch { /* Early failures may not create the private report; the original error remains fatal. */ }
|
||||
}
|
||||
try { console.error(docker('logs', name).replaceAll(/dune-admin\.[0-9a-f-]{36}\.[0-9a-f]{64}/g, '[REDACTED ADMIN KEY]')); } catch { /* May not exist yet. */ }
|
||||
|
||||
Reference in new issue
Block a user