// Read-only production drift probe. No authentication, writes, payments or // provider calls are performed. It intentionally fails when a required // deployment contract is missing. import { mkdir, writeFile } from 'node:fs/promises'; const base = String(process.argv[2] || process.env.IHALEIST_LIVE_URL || 'https://ihaleist.com').replace(/\/$/, ''); const evidencePath = process.env.IHALEIST_LIVE_EVIDENCE_PATH || 'docs/quality/evidence/qa-live-runtime.json'; const timeoutMs = 15_000; const requiredHeaders = [ 'x-content-type-options', 'referrer-policy', 'permissions-policy', 'x-frame-options', 'content-security-policy', ...(base.startsWith('https://') ? ['strict-transport-security'] : []), ]; const observedHeaders = [...new Set([...requiredHeaders, 'strict-transport-security'])]; const checks = {}; async function probe(path, successField = null) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetch(base + path, { signal: controller.signal, redirect: 'manual' }); const result = { status: response.status, contentType: response.headers.get('content-type'), headers: Object.fromEntries(observedHeaders.map(name => [name, response.headers.get(name)])), }; if (successField) { const raw = await response.text(); try { result.json = JSON.parse(raw); } catch { result.jsonError = 'JSON yanıtı ayrıştırılamadı.'; } result.apiSuccess = result.json?.[successField] === true; } return result; } catch (error) { return { error: error.name === 'AbortError' ? `timeout after ${timeoutMs}ms` : error.message }; } finally { clearTimeout(timer); } } checks.home = await probe('/'); checks.health = await probe('/api/health', 'ok'); // /api/health and /api/ready use the same { ok: true } probe contract. The // readiness payload also includes a separate `ready` diagnostic field. checks.ready = await probe('/api/ready', 'ok'); // The EKAP document fallback depends on the bundled browser being available; // check its public capability endpoint without opening a document or mutating // any account data. checks.documents = await probe('/api/documents/config', 'tarayiciVar'); const missingHeaders = requiredHeaders.filter(name => [checks.home, checks.health, checks.ready, checks.documents].some(check => check?.headers?.[name] == null)); const report = { base, readOnly: true, checkedAt: new Date().toISOString(), checks, pass: checks.home?.status === 200 && checks.health?.status === 200 && checks.health.apiSuccess === true && checks.ready?.status === 200 && checks.ready.apiSuccess === true && checks.documents?.status === 200 && checks.documents.apiSuccess === true && missingHeaders.length === 0, missingHeaders, }; await mkdir('docs/quality/evidence', { recursive: true }); await writeFile(evidencePath, JSON.stringify(report, null, 2) + '\n'); console.log(JSON.stringify(report)); if (!report.pass) process.exitCode = 1;