diff --git a/dashcaddy-api/scripts/p7-live-curl.js b/dashcaddy-api/scripts/p7-live-curl.js index 43d63a2..eebb7db 100644 --- a/dashcaddy-api/scripts/p7-live-curl.js +++ b/dashcaddy-api/scripts/p7-live-curl.js @@ -1,76 +1,90 @@ /** - * Live transcript generator for the P7 network routes (judge r7 blocker 2): - * boots the REAL app (createApp) on an ephemeral port and issues actual - * HTTP requests over a real socket. Fresh boot = no active provider, which - * demonstrates the inactive-provider outcome honestly. Run on dns2: - * timeout 90 node scripts/p7-live-curl.js 2>/dev/null + * Live transcript, phase 1: inactive-provider + auth-gate outcomes over a + * real socket. Self-authenticating: prints git HEAD and ASSERTS it matches + * PINNED_ANCESTOR lineage, asserts every expected status/body, exits non-zero on any + * mismatch. Booted with the REAL app (createApp); fresh boot = no active + * provider. */ 'use strict'; const http = require('http'); +const { execSync } = require('child_process'); const { createApp } = require('../src/app'); +// Exact-head evidence: HEAD must descend from the pinned functional commit +// (a commit cannot embed its own hash; the exact HEAD is printed at runtime). +const PINNED_ANCESTOR = '51ba85fc69249f4db0e9cc1d17594882ca59aa57'; + function req(port, method, path, headers = {}) { return new Promise((resolve, reject) => { - const r = http.request( - { host: '127.0.0.1', port, method, path, headers }, - (res) => { - let body = ''; - res.on('data', (c) => { body += c; }); - res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body })); - } - ); + const r = http.request({ host: '127.0.0.1', port, method, path, headers }, (res) => { + let body = ''; + res.on('data', (c) => { body += c; }); + res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body })); + }); r.on('error', reject); r.end(); }); } +let FAILURES = 0; +function expect(name, actual, predicate, rendered) { + const ok = predicate(actual); + console.log(`< assert ${name}: ${ok ? 'PASS' : 'FAIL'} (${rendered || actual})`); + if (!ok) FAILURES += 1; +} + async function main() { + const head = execSync('git -C .. rev-parse HEAD', { encoding: 'utf8' }).trim(); + console.log(`# git HEAD: ${head} (must descend from ${PINNED_ANCESTOR.slice(0, 7)})`); + const anc = execSync(`git -C .. merge-base --is-ancestor ${PINNED_ANCESTOR} HEAD && echo yes`, { encoding: 'utf8' }).trim(); + expect('head.on-p7-lineage', anc, (a) => a === 'yes'); + const { app } = await createApp(); const server = app.listen(0, '127.0.0.1'); await new Promise((r) => server.on('listening', r)); const port = server.address().port; - const { execSync } = require('child_process'); - console.log(`# git HEAD: ${execSync('git -C .. rev-parse HEAD', { encoding: 'utf8' }).trim()}`); const show = (label, method, path, headers, res) => { console.log(`### ${label}`); console.log(`> ${method} http://127.0.0.1:${port}${path}`); for (const [k, v] of Object.entries(headers)) console.log(`> ${k}: ${v}`); console.log(`< HTTP ${res.status}`); - if (res.headers['www-authenticate']) console.log(`< www-authenticate: ${res.headers['www-authenticate']}`); - if (res.headers['x-forwarded-user']) console.log(`< x-forwarded-user: ${res.headers['x-forwarded-user']}`); console.log(`< body: ${res.body.slice(0, 260)}`); console.log(''); }; const FA = '/api/v1/network/forward-auth/'; - // 1. inactive provider (fresh boot) + supplied XFF let res = await req(port, 'GET', FA, { 'X-Forwarded-For': '10.53.0.7' }); show('forward-auth, provider inactive (fresh boot) → 503 network provider not active', 'GET', FA, { 'X-Forwarded-For': '10.53.0.7' }, res); + expect('fa-inactive-xf.status=503', res.status, (s) => s === 503); + expect('fa-inactive-xf.error', res.body, (b) => b.includes('network provider not active')); - // 2. direct request, no headers, provider inactive (socket identity path — still 503 pre-activation; the 401 unknown-device variant requires an active provider and is covered by the jest suite's adversarial tests) res = await req(port, 'GET', FA); show('forward-auth, direct request (no headers), provider inactive → 503', 'GET', FA, {}, res); + expect('fa-inactive-direct.status=503', res.status, (s) => s === 503); - // 3. POST parity res = await req(port, 'POST', FA, { 'X-Forwarded-For': '10.53.0.7', 'X-Forwarded-Host': 'plex.sami' }); show('forward-auth POST parity, provider inactive → 503', 'POST', FA, { 'X-Forwarded-For': '10.53.0.7', 'X-Forwarded-Host': 'plex.sami' }, res); + expect('fa-inactive-post.status=503', res.status, (s) => s === 503); - // 4. mounted-route proofs behind the auth gate (unauthenticated) res = await req(port, 'GET', '/api/v1/network/people'); - show('people list, unauthenticated → auth gate (route mounted, gated)', 'GET', '/api/v1/network/people', {}, res); + show('people list, unauthenticated → auth gate 401 (route mounted, gated)', 'GET', '/api/v1/network/people', {}, res); + expect('people-gate.status=401', res.status, (s) => s === 401); res = await req(port, 'GET', '/api/v1/network/wizard/state'); - show('wizard state, unauthenticated → auth gate (route mounted, gated)', 'GET', '/api/v1/network/wizard/state', {}, res); + show('wizard state, unauthenticated → auth gate 401 (route mounted, gated)', 'GET', '/api/v1/network/wizard/state', {}, res); + expect('wizard-gate.status=401', res.status, (s) => s === 401); res = await req(port, 'GET', '/api/v1/network/wg/redeem/nonexistent-token'); - show('wg redeem, bogus token (public route) → redeem outcome (route mounted)', 'GET', '/api/v1/network/wg/redeem/nonexistent-token', {}, res); + show('wg redeem, bogus token (public route) → 503 (route mounted, sessionless)', 'GET', '/api/v1/network/wg/redeem/nonexistent-token', {}, res); + expect('wg-redeem-gate.status=503', res.status, (s) => s === 503); + expect('wg-redeem-gate.error', res.body, (b) => b.includes('network provider not active')); await new Promise((r) => server.close(r)); - console.log('LIVE-PROBE-DONE'); - process.exit(0); // background timers would hold the loop open + console.log(`# asserts: ${FAILURES === 0 ? 'ALL PASS' : FAILURES + ' FAILURES'}`); + process.exit(FAILURES === 0 ? 0 : 1); } main().catch((e) => { console.error('FATAL', e && e.message); process.exit(1); }); diff --git a/dashcaddy-api/scripts/p7-live-curl2.js b/dashcaddy-api/scripts/p7-live-curl2.js index 2a1e9bd..5eb64df 100644 --- a/dashcaddy-api/scripts/p7-live-curl2.js +++ b/dashcaddy-api/scripts/p7-live-curl2.js @@ -12,6 +12,9 @@ 'use strict'; const http = require('http'); +// Exact-head evidence: HEAD must descend from the pinned functional commit +// (a commit cannot embed its own hash; the exact HEAD is printed at runtime). +const PINNED_ANCESTOR = '51ba85fc69249f4db0e9cc1d17594882ca59aa57'; const fs = require('fs'); const os = require('os'); const path = require('path'); @@ -47,7 +50,9 @@ async function main() { let server = null; try { const head = execSync('git -C .. rev-parse HEAD', { encoding: 'utf8' }).trim(); - console.log(`# git HEAD: ${head}`); + console.log(`# git HEAD: ${head} (must descend from ${PINNED_ANCESTOR.slice(0, 7)})`); + const anc = execSync(`git -C .. merge-base --is-ancestor ${PINNED_ANCESTOR} HEAD && echo yes`, { encoding: 'utf8' }).trim(); + expect('head.on-p7-lineage', anc, (a) => a === 'yes'); console.log(`# isolated DATA_DIR: ${TMP}`); const now = new Date().toISOString(); @@ -145,7 +150,11 @@ async function main() { process.exit(FAILURES === 0 ? 0 : 1); } -main().catch(async (e) => { +main().catch((e) => { console.error('FATAL', e && e.stack || e); + // Pre-finally failures (init, requires) can bypass main()'s finally: + // guarantee the isolated dir is gone on this path too. + try { fs.rmSync(TMP, { recursive: true, force: true }); } catch (_) { /* best effort */ } + console.log(`# cleanup (rejection path): removed ${TMP}`); process.exit(1); });