import { execFileSync } from 'node:child_process' import { randomUUID } from 'node:crypto' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' const workspaceRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..') const supabaseDirectory = resolve(workspaceRoot, 'server') const contentReportPath = '/functions/v1/content-report' const dummyGenerationId = '11111111-1111-4111-8111-111111111111' const minimumScenarioCount = 500 const fixturePassword = 'D3ro-Red-E2E-2026!' function fail(code) { throw new Error(`content_report_red_e2e_failed:${code}`) } function assert(condition, code) { if (!condition) fail(code) } function readLocalSupabaseEnvironment() { const output = execFileSync('supabase', ['status', '--workdir', supabaseDirectory, '-o', 'env'], { cwd: workspaceRoot, encoding: 'utf8', windowsHide: true }) const values = new Map() for (const line of output.split(/\r?\n/)) { const separator = line.indexOf('=') if (separator <= 0) continue values.set(line.slice(0, separator), line.slice(separator + 1).replace(/^"|"$/g, '')) } const baseUrl = values.get('API_URL') const anonKey = values.get('PUBLISHABLE_KEY') ?? values.get('ANON_KEY') const serviceKey = values.get('SECRET_KEY') ?? values.get('SERVICE_ROLE_KEY') const legacyServiceRoleKey = values.get('SERVICE_ROLE_KEY') assert(baseUrl === 'http://127.0.0.1:55321', 'local_stack_endpoint_required') assert(anonKey?.startsWith('sb_publishable_'), 'local_publishable_key_required') assert(serviceKey?.startsWith('sb_secret_'), 'local_service_key_required') assert(legacyServiceRoleKey?.startsWith('eyJ'), 'local_legacy_service_role_key_required') return { baseUrl, anonKey, serviceKey, legacyServiceRoleKey } } const environment = readLocalSupabaseEnvironment() const cleanupUserIds = new Set() const scenarioCounts = new Map() let completedScenarios = 0 function headersForUser(accessToken, extra = {}) { return { apikey: environment.anonKey, ...(accessToken === undefined ? {} : { Authorization: `Bearer ${accessToken}` }), ...extra } } function serviceHeaders(extra = {}) { return { apikey: environment.serviceKey, Authorization: `Bearer ${environment.serviceKey}`, ...extra } } // Local GoTrue admin-user deletion accepts the legacy service_role JWT. The // suite uses it only for disposable local fixture cleanup; all PostgREST/RPC // service operations use the current sb_secret_ key above. function authAdminHeaders(extra = {}) { return { apikey: environment.legacyServiceRoleKey, Authorization: `Bearer ${environment.legacyServiceRoleKey}`, ...extra } } async function request(path, init = {}) { const response = await fetch(`${environment.baseUrl}${path}`, init) const text = await response.text() let json = null if (text.length > 0) { try { json = JSON.parse(text) } catch { json = null } } return { status: response.status, cacheControl: response.headers.get('cache-control'), json } } function baseReportBody(overrides = {}) { return { kind: 'ai_output', source: { type: 'talk_response', generationId: dummyGenerationId }, reason: 'privacy', snapshot: 'Reporter-selected red E2E evidence', ...overrides } } function contentReportRequest({ accessToken, method = 'POST', contentType = 'application/json', idempotencyKey = randomUUID(), omitContentType = false, omitIdempotencyKey = false, body = baseReportBody() }) { const headers = headersForUser(accessToken, { ...(omitContentType ? {} : { 'Content-Type': contentType }), ...(omitIdempotencyKey ? {} : { 'Idempotency-Key': idempotencyKey }) }) return request(contentReportPath, { method, headers, ...(body === undefined || method === 'GET' || method === 'HEAD' ? {} : { body: JSON.stringify(body) }) }) } function expectResponse(response, scenario) { assert(response.cacheControl === 'no-store', `${scenario.id}:cache_control`) assert(response.status === scenario.status, `${scenario.id}:status_${response.status}`) if (scenario.code !== undefined) { assert(response.json?.error === scenario.code, `${scenario.id}:code`) } } function addScenario(scenarios, family, id, requestFactory, status, code) { scenarios.push({ family, id, requestFactory, status, code }) } function buildStatelessRedScenarios() { const scenarios = [] for (const method of ['GET', 'PUT', 'PATCH', 'DELETE']) { addScenario( scenarios, 'method', `RED-METHOD-${method}`, (context) => contentReportRequest({ accessToken: context.primary.accessToken, method, body: undefined }), 405, 'method_not_allowed' ) } const invalidContentTypes = [ { contentType: 'text/plain' }, { contentType: 'application/jsonp' }, { contentType: 'application/xml' }, { contentType: 'multipart/form-data' }, { contentType: 'application/octet-stream' }, { omitContentType: true } ] for (const [index, options] of invalidContentTypes.entries()) { addScenario( scenarios, 'content_type', `RED-CONTENT-TYPE-${String(index).padStart(2, '0')}`, (context) => contentReportRequest({ accessToken: context.primary.accessToken, ...options }), 415, 'content_type_must_be_json' ) } for (const [index, accessToken] of [ undefined, environment.anonKey, 'not-a-valid-access-token' ].entries()) { addScenario( scenarios, 'authentication', `RED-AUTH-${String(index).padStart(2, '0')}`, () => contentReportRequest({ accessToken }), 401 ) } addScenario( scenarios, 'idempotency', 'RED-IDEMPOTENCY-MISSING', (context) => contentReportRequest({ accessToken: context.primary.accessToken, omitIdempotencyKey: true }), 400, 'idempotency_key_required' ) for (let index = 0; index < 95; index += 1) { addScenario( scenarios, 'idempotency', `RED-IDEMPOTENCY-INVALID-${String(index).padStart(3, '0')}`, (context) => contentReportRequest({ accessToken: context.primary.accessToken, idempotencyKey: `invalid-idempotency-${index}` }), 400, 'invalid_request' ) } for (const requiredKey of ['kind', 'source', 'reason', 'snapshot']) { addScenario( scenarios, 'root_shape', `RED-ROOT-MISSING-${requiredKey.toUpperCase()}`, (context) => { const body = baseReportBody() delete body[requiredKey] return contentReportRequest({ accessToken: context.primary.accessToken, body }) }, 400, 'invalid_request' ) } for (let index = 0; index < 60; index += 1) { addScenario( scenarios, 'root_shape', `RED-ROOT-UNEXPECTED-${String(index).padStart(3, '0')}`, (context) => contentReportRequest({ accessToken: context.primary.accessToken, body: { ...baseReportBody(), [`unexpected_${index}`]: { index } } }), 400, 'invalid_request' ) } for (let index = 0; index < 64; index += 1) { addScenario( scenarios, 'kind', `RED-KIND-${String(index).padStart(3, '0')}`, (context) => contentReportRequest({ accessToken: context.primary.accessToken, body: baseReportBody({ kind: `non_ai_output_${index}` }) }), 400, 'invalid_request' ) } for (let index = 0; index < 96; index += 1) { addScenario( scenarios, 'source', `RED-SOURCE-${String(index).padStart(3, '0')}`, (context) => { let source if (index < 32) { source = { type: `unknown_surface_${index}`, generationId: dummyGenerationId } } else if (index < 64) { source = { type: 'talk_response', generationId: `not-a-uuid-${index}` } } else { const malformedSources = [ null, false, index, `source-${index}`, [], {}, { type: 'talk_response' }, { generationId: dummyGenerationId } ] source = malformedSources[index % malformedSources.length] } return contentReportRequest({ accessToken: context.primary.accessToken, body: baseReportBody({ source }) }) }, 400, 'invalid_request' ) } for (let index = 0; index < 96; index += 1) { addScenario( scenarios, 'reason', `RED-REASON-${String(index).padStart(3, '0')}`, (context) => contentReportRequest({ accessToken: context.primary.accessToken, body: baseReportBody({ reason: `unsupported_reason_${index}` }) }), 400, 'invalid_request' ) } for (let index = 0; index < 96; index += 1) { addScenario( scenarios, 'snapshot', `RED-SNAPSHOT-${String(index).padStart(3, '0')}`, (context) => { let snapshot if (index < 32) snapshot = ' '.repeat(index + 1) else if (index < 64) snapshot = 'x'.repeat(4_001 + index) else { const nonText = [null, false, index, { index }, [index], `\n${' '.repeat(index)}`] snapshot = nonText[index % nonText.length] } return contentReportRequest({ accessToken: context.primary.accessToken, body: baseReportBody({ snapshot }) }) }, 400, 'invalid_request' ) } for (let index = 0; index < 96; index += 1) { addScenario( scenarios, 'comment', `RED-COMMENT-${String(index).padStart(3, '0')}`, (context) => { let comment if (index < 32) comment = ' '.repeat(index + 1) else if (index < 64) comment = 'x'.repeat(501 + index) else { const nonText = [null, false, index, { index }, [index], `\t${' '.repeat(index)}`] comment = nonText[index % nonText.length] } return contentReportRequest({ accessToken: context.primary.accessToken, body: baseReportBody({ comment }) }) }, 400, 'invalid_request' ) } return scenarios } async function runScenarioBatch(scenarios, context) { for (let offset = 0; offset < scenarios.length; offset += 12) { await Promise.all( scenarios.slice(offset, offset + 12).map(async (scenario) => { const response = await scenario.requestFactory(context) expectResponse(response, scenario) completedScenarios += 1 scenarioCounts.set(scenario.family, (scenarioCounts.get(scenario.family) ?? 0) + 1) }) ) } } async function createFixtureUser(label) { const email = `content-report-red-${label}-${randomUUID().replaceAll('-', '')}@example.invalid` const response = await request('/auth/v1/signup', { method: 'POST', headers: headersForUser(environment.anonKey, { 'Content-Type': 'application/json' }), body: JSON.stringify({ email, password: fixturePassword }) }) assert(response.status === 200, `signup_${label}_status_${response.status}`) assert(typeof response.json?.user?.id === 'string', `signup_${label}_user_id`) assert(typeof response.json?.access_token === 'string', `signup_${label}_access_token`) cleanupUserIds.add(response.json.user.id) return { id: response.json.user.id, accessToken: response.json.access_token } } async function issueReceipt(userId, purpose) { const response = await request('/rest/v1/rpc/issue_content_generation_receipt_v1', { method: 'POST', headers: serviceHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ p_actor_id: userId, p_purpose: purpose, p_model: 'content-report-red-e2e' }) }) assert(response.status === 200, `receipt_${purpose}_status_${response.status}`) assert(typeof response.json?.generationId === 'string', `receipt_${purpose}_generation_id`) return response.json.generationId } async function submitValidReport( user, sourceType, generationId, reason, suffix, idempotencyKey = randomUUID() ) { const payload = { kind: 'ai_output', source: { type: sourceType, generationId }, reason, comment: `Red E2E ${suffix}`, snapshot: `Reporter-selected Red E2E evidence ${suffix}` } const response = await contentReportRequest({ accessToken: user.accessToken, idempotencyKey, body: payload }) return { response, payload, idempotencyKey } } async function runStatefulScenario(id, family, action) { await action() completedScenarios += 1 scenarioCounts.set(family, (scenarioCounts.get(family) ?? 0) + 1) } async function configureManager(user) { const profile = await request(`/rest/v1/profiles?id=eq.${user.id}`, { method: 'PATCH', headers: serviceHeaders({ 'Content-Type': 'application/json', Prefer: 'return=representation' }), body: JSON.stringify({ role: 'manager' }) }) assert(profile.status === 200, `manager_profile_status_${profile.status}`) } async function runStatefulRedScenarios(context) { const sources = [] for (const [index, sourceType] of [ 'talk_response', 'command_response', 'action_response' ].entries()) { const generationId = await issueReceipt(context.primary.id, sourceType) const scenario = await submitValidReport( context.primary, sourceType, generationId, ['harmful', 'privacy', 'spam'][index], `source-${sourceType}` ) expectResponse(scenario.response, { id: `STATE-VALID-${sourceType}`, status: 201 }) assert(scenario.response.json?.idempotent === false, `STATE-VALID-${sourceType}:not_idempotent`) sources.push({ sourceType, generationId, ...scenario }) await runStatefulScenario(`STATE-VALID-${sourceType}`, 'valid_source', async () => {}) } const first = sources[0] await runStatefulScenario('STATE-IDEMPOTENT-REPLAY', 'idempotency_state', async () => { const replay = await contentReportRequest({ accessToken: context.primary.accessToken, idempotencyKey: first.idempotencyKey, body: first.payload }) expectResponse(replay, { id: 'STATE-IDEMPOTENT-REPLAY', status: 200 }) assert(replay.json?.idempotent === true, 'STATE-IDEMPOTENT-REPLAY:not_idempotent') }) await runStatefulScenario('STATE-IDEMPOTENCY-CONFLICT', 'idempotency_state', async () => { const conflict = await contentReportRequest({ accessToken: context.primary.accessToken, idempotencyKey: first.idempotencyKey, body: { ...first.payload, snapshot: `${first.payload.snapshot} changed` } }) expectResponse(conflict, { id: 'STATE-IDEMPOTENCY-CONFLICT', status: 409, code: 'idempotency_conflict' }) }) await runStatefulScenario('STATE-DUPLICATE-SOURCE', 'source_ownership', async () => { const duplicate = await contentReportRequest({ accessToken: context.primary.accessToken, body: first.payload }) expectResponse(duplicate, { id: 'STATE-DUPLICATE-SOURCE', status: 409, code: 'source_already_reported' }) }) await runStatefulScenario('STATE-CROSS-USER-SOURCE', 'source_ownership', async () => { const crossUser = await contentReportRequest({ accessToken: context.secondary.accessToken, body: { ...first.payload, snapshot: 'Cross-user source attempt' } }) expectResponse(crossUser, { id: 'STATE-CROSS-USER-SOURCE', status: 404, code: 'source_not_found' }) }) await runStatefulScenario('STATE-SOURCE-PURPOSE-MISMATCH', 'source_ownership', async () => { const mismatch = await contentReportRequest({ accessToken: context.primary.accessToken, body: { ...first.payload, source: { type: 'action_response', generationId: first.generationId }, snapshot: 'Purpose mismatch attempt' } }) expectResponse(mismatch, { id: 'STATE-SOURCE-PURPOSE-MISMATCH', status: 404, code: 'source_not_found' }) }) await runStatefulScenario('STATE-UNKNOWN-SOURCE', 'source_ownership', async () => { const unknownSource = await contentReportRequest({ accessToken: context.primary.accessToken, body: { ...first.payload, source: { type: 'talk_response', generationId: randomUUID() }, snapshot: 'Unknown source attempt' } }) expectResponse(unknownSource, { id: 'STATE-UNKNOWN-SOURCE', status: 404, code: 'source_not_found' }) }) await runStatefulScenario('STATE-DIRECT-LEDGER-READ', 'ledger_acl', async () => { const directRead = await request('/rest/v1/content_reports?select=id', { headers: headersForUser(context.primary.accessToken) }) assert(directRead.status === 403, `STATE-DIRECT-LEDGER-READ:status_${directRead.status}`) }) for (let index = 0; index < 11; index += 1) { await runStatefulScenario( `STATE-RATE-${String(index + 1).padStart(2, '0')}`, 'rate_limit', async () => { const generationId = await issueReceipt(context.rateLimited.id, 'talk_response') const submission = await submitValidReport( context.rateLimited, 'talk_response', generationId, 'other', `rate-${index}` ) expectResponse(submission.response, { id: `STATE-RATE-${String(index + 1).padStart(2, '0')}`, status: index < 10 ? 201 : 429, ...(index < 10 ? {} : { code: 'report_rate_limited' }) }) } ) } await configureManager(context.manager) let reportId await runStatefulScenario('STATE-ORDINARY-MODERATION-DENIED', 'moderation_acl', async () => { const ordinary = await request('/rest/v1/rpc/admin_list_content_reports_v1', { method: 'POST', headers: headersForUser(context.primary.accessToken, { 'Content-Type': 'application/json' }), body: JSON.stringify({ p_status: 'pending', p_limit: 100, p_before: null }) }) assert(ordinary.status === 403, `STATE-ORDINARY-MODERATION-DENIED:status_${ordinary.status}`) }) await runStatefulScenario('STATE-MANAGER-QUEUE-READ', 'moderation_acl', async () => { const queue = await request('/rest/v1/rpc/admin_list_content_reports_v1', { method: 'POST', headers: headersForUser(context.manager.accessToken, { 'Content-Type': 'application/json' }), body: JSON.stringify({ p_status: 'pending', p_limit: 100, p_before: null }) }) assert(queue.status === 200, `STATE-MANAGER-QUEUE-READ:status_${queue.status}`) reportId = queue.json?.find((report) => report.source_id === first.generationId)?.report_id assert(typeof reportId === 'string', 'STATE-MANAGER-QUEUE-READ:report_id') }) const reviewKey = randomUUID() await runStatefulScenario('STATE-MANAGER-BEGIN-REVIEW', 'moderation_transition', async () => { const review = await request('/rest/v1/rpc/admin_act_on_content_report_v1', { method: 'POST', headers: headersForUser(context.manager.accessToken, { 'Content-Type': 'application/json' }), body: JSON.stringify({ p_report_id: reportId, p_idempotency_key: reviewKey, p_action: 'begin_review', p_note: 'Red E2E review started' }) }) assert( review.status === 200 && review.json?.status === 'reviewing', 'STATE-MANAGER-BEGIN-REVIEW:result' ) }) await runStatefulScenario('STATE-MANAGER-REVIEW-REPLAY', 'moderation_transition', async () => { const replay = await request('/rest/v1/rpc/admin_act_on_content_report_v1', { method: 'POST', headers: headersForUser(context.manager.accessToken, { 'Content-Type': 'application/json' }), body: JSON.stringify({ p_report_id: reportId, p_idempotency_key: reviewKey, p_action: 'begin_review', p_note: 'Red E2E review started' }) }) assert( replay.status === 200 && replay.json?.idempotent === true, 'STATE-MANAGER-REVIEW-REPLAY:result' ) }) await runStatefulScenario('STATE-MANAGER-REVIEW-CONFLICT', 'moderation_transition', async () => { const conflict = await request('/rest/v1/rpc/admin_act_on_content_report_v1', { method: 'POST', headers: headersForUser(context.manager.accessToken, { 'Content-Type': 'application/json' }), body: JSON.stringify({ p_report_id: reportId, p_idempotency_key: reviewKey, p_action: 'begin_review', p_note: 'Changed review note' }) }) assert(conflict.status === 409, `STATE-MANAGER-REVIEW-CONFLICT:status_${conflict.status}`) }) await runStatefulScenario('STATE-MANAGER-DISMISS', 'moderation_transition', async () => { const dismiss = await request('/rest/v1/rpc/admin_act_on_content_report_v1', { method: 'POST', headers: headersForUser(context.manager.accessToken, { 'Content-Type': 'application/json' }), body: JSON.stringify({ p_report_id: reportId, p_idempotency_key: randomUUID(), p_action: 'dismiss', p_note: 'No policy violation in selected evidence' }) }) assert( dismiss.status === 200 && dismiss.json?.status === 'dismissed', 'STATE-MANAGER-DISMISS:result' ) }) await runStatefulScenario('STATE-RESOLVED-REPORT-LOCKED', 'moderation_transition', async () => { const resolved = await request('/rest/v1/rpc/admin_act_on_content_report_v1', { method: 'POST', headers: headersForUser(context.manager.accessToken, { 'Content-Type': 'application/json' }), body: JSON.stringify({ p_report_id: reportId, p_idempotency_key: randomUUID(), p_action: 'dismiss', p_note: 'Repeat terminal transition denied' }) }) assert(resolved.status === 409, `STATE-RESOLVED-REPORT-LOCKED:status_${resolved.status}`) }) await runStatefulScenario( 'STATE-ORDINARY-MODERATION-ACTION-DENIED', 'moderation_acl', async () => { const ordinary = await request('/rest/v1/rpc/admin_act_on_content_report_v1', { method: 'POST', headers: headersForUser(context.primary.accessToken, { 'Content-Type': 'application/json' }), body: JSON.stringify({ p_report_id: reportId, p_idempotency_key: randomUUID(), p_action: 'begin_review', p_note: 'Ordinary user must not review reports' }) }) assert( ordinary.status === 403, `STATE-ORDINARY-MODERATION-ACTION-DENIED:status_${ordinary.status}` ) } ) } async function cleanup() { const failures = [] for (const userId of cleanupUserIds) { const reports = await request(`/rest/v1/content_reports?reporter_id=eq.${userId}`, { method: 'DELETE', headers: serviceHeaders() }) if (![200, 204].includes(reports.status)) failures.push(`report_delete_${reports.status}`) } for (const userId of cleanupUserIds) { const user = await request(`/auth/v1/admin/users/${userId}`, { method: 'DELETE', headers: authAdminHeaders() }) if (![200, 204].includes(user.status)) failures.push(`user_delete_${user.status}`) } assert(failures.length === 0, `fixture_cleanup_${failures.join('_')}`) } try { const context = { primary: await createFixtureUser('primary'), secondary: await createFixtureUser('secondary'), rateLimited: await createFixtureUser('rate'), manager: await createFixtureUser('manager') } const statelessScenarios = buildStatelessRedScenarios() assert(statelessScenarios.length === 621, `stateless_scenario_count_${statelessScenarios.length}`) await runScenarioBatch(statelessScenarios, context) await runStatefulRedScenarios(context) assert(completedScenarios >= minimumScenarioCount, `minimum_scenario_count_${completedScenarios}`) assert(completedScenarios === 650, `exact_scenario_count_${completedScenarios}`) console.log( JSON.stringify({ suite: 'content-report-red-e2e', localOnly: true, scenarios: completedScenarios, families: Object.fromEntries( [...scenarioCounts.entries()].sort(([left], [right]) => left.localeCompare(right)) ), cleanupUsers: cleanupUserIds.size }) ) } finally { await cleanup() }