Files
SGF/scripts/payable-evidence-http.mjs
T
2026-11-04 09:37:20 +02:00

70 lines
5.8 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import net from 'node:net';
import {spawn} from 'node:child_process';
import assert from 'node:assert/strict';
import crypto from 'node:crypto';
const state = JSON.parse(fs.readFileSync('C:/laragon/data/sgf-tempo/run/modular-regression-current.json', 'utf8'));
const tag = process.argv[2];
if (state.port !== 55439 || !/^[a-f0-9]{12}$/.test(tag ?? '')) throw Error('Owned QA context required');
const root = path.join(state.root, 'private/payable-history-' + tag);
const settings = path.join(root, 'settings.json');
const owned = JSON.parse(fs.readFileSync(settings, 'utf8'));
if (owned.tag !== tag || owned.cluster !== 'sgf_modular_regression_' + state.id) throw Error('Owned cluster mismatch');
if (fs.existsSync(path.join(root, 'evidence-http.json'))) throw Error('Completed proof exists; preserve it instead of repeating');
await new Promise((resolve, reject) => {
const probe = net.createServer(); probe.once('error', () => reject(Error('Owned proof port unavailable')));
probe.listen(8047, '127.0.0.1', () => probe.close(resolve));
});
const log = fs.openSync(path.join(root, 'evidence-http-server.log'), 'a');
const server = spawn('C:/laragon/tools/sgf-tempo/php-8.4.27/php.exe', ['-S', '127.0.0.1:8047', path.resolve('scripts/payable-history-sgf-http-router.php')], {
cwd: process.cwd(), env: {...process.env, PAYABLE_HISTORY_QA_SETTINGS: settings}, windowsHide: true, stdio: ['ignore', log, log],
});
let checks = 0;
const check = (ok, why) => { checks++; assert(ok, why); };
const jar = new Map(); let auth;
async function request(url, body, extra = {}) {
const headers = {Accept: 'application/json', Cookie: [...jar].map(([k,v]) => k + '=' + v).join('; '), 'X-SGF-Context': auth?.context ?? '', ...extra};
if (body) Object.assign(headers, {'Content-Type': 'application/json', 'X-CSRF-TOKEN': auth?.csrf ?? '', 'Idempotency-Key': crypto.randomUUID()});
const r = await fetch('http://127.0.0.1:8047' + url, {method: body ? 'POST' : 'GET', headers, body: body ? JSON.stringify(body) : undefined, redirect: 'manual', signal: AbortSignal.timeout(8000)});
for (const cookie of r.headers.getSetCookie()) { const pair = cookie.split(';')[0], at = pair.indexOf('='); jar.set(pair.slice(0, at), pair.slice(at + 1)); }
const text = await r.text(); let data; try { data = JSON.parse(text); } catch {}
return {status: r.status, text, data, headers: r.headers};
}
try {
for (let n = 0; n < 50 && !auth; n++) {
try { const entry = await request('/qa/enter/read'); if (entry.status === 200) auth = entry.data; } catch {}
if (!auth) await new Promise(resolve => setTimeout(resolve, 100));
}
check(Boolean(auth), 'Owned QA entry');
const id = auth.source_id;
const focused = await request('/api/supplier-payables?source=' + id);
check(focused.status === 200 && focused.data.documents.length === 1 && focused.data.documents[0].local_id === id, 'Exact source through real HTTP kernel');
check(focused.data.obligations.length === 1 && focused.data.obligations[0].balance_minor === 3000, 'Recognised original position');
check(focused.data.pagination.documents.total === 1 && focused.data.listing_query.source === id, 'Scoped source total and metadata');
const page = await request('/api/supplier-payables?source=' + id + '&proposal_page=2&event_page=2');
check(page.status === 200 && page.data.documents[0].local_id === id && page.data.obligations[0].balance_minor === 3000, 'Evidence pages leave source and position fixed');
const absent = await request('/api/supplier-payables?source=' + id + '&recognition=unrecognised');
check(absent.status === 200 && absent.data.documents.length === 0 && absent.data.events.length === 0, 'Combined predicate excludes all other evidence');
for (const query of ['source=0', 'source=-1', 'source=', 'source%5Ba%5D=1', 'source=true', 'source=999999999999999999999999999999']) {
check((await request('/api/supplier-payables?' + query)).status === 422, 'Invalid source rejected');
}
check((await request('/api/supplier-payables?source=9223372036854775807')).status === 403, 'Unavailable source does not disclose existence');
check((await request('/api/supplier-payables?source=' + id, undefined, {'X-SGF-Context': 'obsolete'})).status === 409, 'Focus does not bypass current context');
check((await request('/api/supplier-payables/prepare_history', {id, reason: 'Focused source must not grant write rights.'})).status === 403, 'Read-only source link grants no command rights');
const html = await request('/supplier-payables?source=' + id);
check(html.status === 200 && html.text.includes('name="source" value="' + id + '"'), 'Rendered form retains source');
check(html.text.includes('Conferência da origem #' + id) && html.text.includes('Consultar todas as origens'), 'Focus and exit visible');
check(html.text.includes('href="/supplier-payables?source=' + id + '"'), 'Document evidence link resolves locally');
const injection = await request('/supplier-payables?source=' + id + '&q=' + encodeURIComponent('<script>evidence_xss</script>'));
check(injection.status === 200 && !injection.text.includes('<script>evidence_xss</script>') && injection.text.includes('&lt;script&gt;evidence_xss&lt;/script&gt;'), 'Focused search remains escaped');
check(focused.headers.get('cache-control').includes('no-store'), 'Private focused response');
const report = {increment: 'SGF-PAYABLE-EVIDENCE-FOCUS-SIM-v1', passed: true, checks, real_http_kernel: true, read_only_actor: true, visual_review: false, browser_interactions_tested: false};
fs.writeFileSync(path.join(root, 'evidence-http.json'), JSON.stringify(report, null, 2) + '\n');
console.log(JSON.stringify(report));
} finally {
if (server.exitCode === null) { server.kill(); await new Promise(resolve => server.once('exit', resolve)); }
fs.closeSync(log);
}