// Thin client for the Provisioner's internal API — same role as // platform/gateway/src/provisioner-client.js. Phase 2B consolidates ALL Provisioner access // (both student launch/reset and admin prepare/destroy/reset) into this one service, per // "environment orchestration calls" being a Platform API responsibility // (docs/PHASE2B_ADMIN_AND_PLATFORM_DATA.md §1). The Gateway no longer calls the Provisioner // directly — it proxies /api/environment/* to here instead. const PROVISIONER_URL = process.env.PROVISIONER_URL || 'http://localhost:4100'; const PROVISIONER_SHARED_SECRET = process.env.PROVISIONER_SHARED_SECRET; if (!PROVISIONER_SHARED_SECRET) { console.error('FATAL: PROVISIONER_SHARED_SECRET is not set. Refusing to start.'); process.exit(1); } async function call(method, path, body) { const res = await fetch(`${PROVISIONER_URL}${path}`, { method, headers: { 'Content-Type': 'application/json', 'X-Provisioner-Key': PROVISIONER_SHARED_SECRET }, body: body ? JSON.stringify(body) : undefined, }); const text = await res.text(); let json; try { json = JSON.parse(text); } catch { json = null; } return { status: res.status, body: json }; } const provisioner = { getEnvironmentByToken: (token) => call('GET', `/environments/by-token/${encodeURIComponent(token)}`), getEnvironment: (id) => call('GET', `/environments/${encodeURIComponent(id)}`), getToken: (id) => call('GET', `/environments/${encodeURIComponent(id)}/token`), listForStudent: (studentId) => call('GET', `/environments?student_id=${encodeURIComponent(studentId)}`), provision: (studentId) => call('POST', '/environments', { student_id: studentId }), reset: (id) => call('POST', `/environments/${encodeURIComponent(id)}/reset`), destroy: (id) => call('DELETE', `/environments/${encodeURIComponent(id)}`), events: (id) => call('GET', `/environments/${encodeURIComponent(id)}/events`), capacity: () => call('GET', '/capacity'), // Phase 4.1 §26 — instructor dashboard capacity state }; module.exports = { provisioner };