#!/usr/bin/env bun import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs' import { join } from 'path' const root = process.cwd() const outRoot = join(root, 'content_population_exports') const responseRoot = join(root, 'data', 'sources', 'provider-responses') const responsePath = join(responseRoot, 'dataforseo-reviews-endpoint-response.json') mkdirSync(outRoot, { recursive: true }) mkdirSync(responseRoot, { recursive: true }) function readJson(path: string, fallback: any = null) { if (!existsSync(path)) return fallback return JSON.parse(readFileSync(path, 'utf8').replace(/^\uFEFF/, '')) } function csv(rows: any[], headers: string[]) { const esc = (value: any) => { if (value === undefined || value === null) value = '' if (Array.isArray(value)) value = value.join(' | ') value = String(value) return /[",\n\r]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value } return [headers.join(','), ...rows.map(row => headers.map(header => esc(row[header])).join(','))].join('\n') + '\n' } const generatedAt = new Date().toISOString() const support = readJson(join(outRoot, 'dataforseo_support_escalation.json'), {}) const preflight = readJson(join(outRoot, 'dataforseo_task_preflight.json'), {}) const approvalRequest = readJson(join(outRoot, 'dataforseo_stage1_approval_request.json'), {}) const template = { provider: 'DataForSEO', endpoint: 'POST https://api.dataforseo.com/v3/merchant/amazon/reviews/task_post', supportTicketId: '', responseReceived: false, responseReceivedAt: '', endpointAvailable: false, replacementEndpoint: '', requiredParameterChanges: '', providerSaysRetrySafe: false, retryScope: 'one capped Stage 1 pilot only after local approval/spend gates pass', credentialIncludedInSupportThread: false, responderName: '', rawResponseSummary: '', notes: '' } if (!existsSync(responsePath)) { writeFileSync(responsePath, JSON.stringify(template, null, 2) + '\n', 'utf8') } const response = readJson(responsePath, template) const hasEndpointPath = response.endpointAvailable === true || Boolean(String(response.replacementEndpoint || '').trim()) const retrySafe = response.providerSaysRetrySafe === true && hasEndpointPath const noSecrets = response.credentialIncludedInSupportThread !== true const rows = [ { Check: 'Support escalation packet ready', Status: support.ready === true ? 'pass' : 'blocked', Blocking: 'yes', Evidence: support.ready === true ? support.issue : 'content_population_exports/dataforseo_support_escalation.md is not ready' }, { Check: 'Provider response recorded', Status: response.responseReceived === true && Boolean(response.responseReceivedAt) ? 'pass' : 'blocked', Blocking: 'yes', Evidence: response.responseReceived === true ? `${response.supportTicketId || 'ticket not recorded'} at ${response.responseReceivedAt || 'missing timestamp'}` : 'data/sources/provider-responses/dataforseo-reviews-endpoint-response.json has no received response' }, { Check: 'Endpoint or replacement path confirmed', Status: hasEndpointPath ? 'pass' : 'blocked', Blocking: 'yes', Evidence: response.endpointAvailable === true ? 'original reviews task_post endpoint marked available' : (response.replacementEndpoint || 'no replacement endpoint recorded') }, { Check: 'Provider says retry is safe', Status: retrySafe ? 'pass' : 'blocked', Blocking: 'yes', Evidence: response.providerSaysRetrySafe === true ? response.retryScope : 'do not retry the paid Reviews post until provider confirms availability/retry path' }, { Check: 'No credentials in support thread', Status: noSecrets ? 'pass' : 'blocked', Blocking: 'yes', Evidence: noSecrets ? 'credentialIncludedInSupportThread=false' : 'support response tracker says credentials were included; rotate/check before continuing' }, { Check: 'Capped local run context still available', Status: Number(preflight.counts?.taskRows || 0) > 0 && Number(approvalRequest.counts?.payloadRows || 0) > 0 ? 'pass' : 'blocked', Blocking: 'yes', Evidence: `${preflight.counts?.taskRows || 0} preflight rows; ${approvalRequest.counts?.payloadRows || 0} approval-request rows` } ] const ready = rows.every(row => row.Status === 'pass') const manifest = { generatedAt, ready, recommendation: ready ? 'Provider availability is recorded. Continue only through approval/spend gates before a single capped retry.' : 'Do not retry DataForSEO Reviews task_post yet. Send/await support response, then record the response in the local provider-response file.', responseFile: 'data/sources/provider-responses/dataforseo-reviews-endpoint-response.json', supportPacket: 'content_population_exports/dataforseo_support_escalation.md', counts: { checks: rows.length, passing: rows.filter(row => row.Status === 'pass').length, blocked: rows.filter(row => row.Status !== 'pass').length }, response, checks: rows, nextActions: rows.filter(row => row.Status !== 'pass').map(row => `${row.Check}: ${row.Evidence}`) } writeFileSync(join(outRoot, 'dataforseo_support_response_gate.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8') writeFileSync(join(outRoot, 'dataforseo_support_response_gate.csv'), csv(rows, ['Check', 'Status', 'Blocking', 'Evidence']), 'utf8') writeFileSync(join(outRoot, 'dataforseo_support_response_gate.md'), [ '# DataForSEO support response gate', '', `Generated: ${generatedAt}`, '', `Ready for capped retry: ${ready ? 'YES' : 'NO'}`, '', manifest.recommendation, '', `Response tracker: \`${manifest.responseFile}\``, `Support packet: \`${manifest.supportPacket}\``, '', '## Checks', ...rows.map(row => `- [${row.Status}] ${row.Check}: ${row.Evidence}`), '', '## Response fields to fill when support replies', '```json', JSON.stringify(template, null, 2), '```', '' ].join('\n'), 'utf8') console.log(`DataForSEO support response gate ready: ${manifest.ready}`) console.log(`Checks passing: ${manifest.counts.passing}/${manifest.counts.checks}`)