import assert from 'node:assert/strict'; import fs from 'node:fs'; const read = file => fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n'); const httpSource = read('apps/api/src/core/http.ts'); const serverSource = read('apps/api/src/server.ts'); const envelopeInterceptorSource = read('apps/api/src/nest/api.interceptor.ts'); const exceptionFilterSource = read('apps/api/src/nest/api-exception.filter.ts'); const apiSource = read('apps/taro/src/services/api.ts'); const typesSource = read('apps/taro/src/types.ts'); const tenantLocatorSource = read('apps/api/src/features/tenant/locator.ts'); const tenantResolutionSource = read('apps/taro/src/app/tenant-resolution.ts'); const studentRouteSource = read('apps/api/src/features/tenant-admin/classes.ts'); const studentCursorSource = read('apps/api/src/features/tenant-admin/student-cursor.ts'); assert.match(httpSource, /meta:\s*\{ \.\.\.existingMeta, requestId \}/, 'API responses must expose requestId without discarding endpoint metadata'); assert.match(serverSource, /useGlobalInterceptors\(new ApiEnvelopeInterceptor\(\)\)/, 'Successful responses must use the global API envelope interceptor'); assert.match(envelopeInterceptorSource, /withResponseMeta\(body, String\(reply\.getHeader\('x-request-id'\)/, 'Successful API responses must carry requestId metadata'); assert.match(serverSource, /useGlobalFilters\(new ApiExceptionFilter\(\)\)/, 'Errors must use the global API exception filter'); assert.match(exceptionFilterSource, /withResponseMeta\(\{ error: message, code, requestId \}, requestId\)/, 'Error API responses must carry the same requestId in the legacy field and metadata envelope'); assert.match(apiSource, /responseHeaderValue\(response\.header, 'x-request-id'\)/, 'The Taro API client must fall back to the response header requestId'); assert.match(apiSource, /this\.requestId = payload\.requestId/, 'ApiError must retain requestId for support and observability'); assert.doesNotMatch(typesSource, /\[key:\s*string\]:\s*unknown/, 'The shared API envelope must not silently accept arbitrary response fields'); assert.match(typesSource, /interface ApiResponseMeta[\s\S]*requestId:\s*string/, 'The Taro response envelope must type requestId metadata'); assert.match(tenantLocatorSource, /TENANT_HOST_CONFLICT/, 'Tenant host conflicts must fail closed'); assert.match(tenantLocatorSource, /TENANT_LOCATOR_REQUIRED/, 'Tenant resolution must reject a missing locator'); assert.match(tenantResolutionSource, /tenantCode:\s*!host \|\| isLocalRuntimeHost\(host\)/, 'Production H5 host resolution must suppress tenantCode overrides'); assert.ok( studentRouteSource.includes('(tm.created_at, tm.id) < ($${params.length - 1}::timestamptz, $${params.length}::uuid)'), 'Deep student pages must use a composite keyset cursor', ); assert.ok( studentRouteSource.includes('order by tm.created_at desc, tm.id desc') && studentRouteSource.includes('limit $${params.length}'), 'Student pagination must keep stable ordering and a bounded limit', ); assert.match(studentRouteSource, /const hasMore = rows\.length > limit/, 'List responses must derive hasMore from a limit+1 query'); assert.match(studentRouteSource, /nextCursor = hasMore && lastItem/, 'List responses must only issue a next cursor when another page exists'); assert.match(studentCursorSource, /parsed\.version !== 1/, 'Opaque cursors must be versioned and fail closed'); const statusContracts = [ ['order', "('pending', 'paid', 'failed', 'closed', 'refunded')", read('supabase/migrations/202606210001_core_multitenant_schema.sql')], ['refund', "('requested', 'approved', 'processing', 'succeeded', 'failed', 'rejected', 'cancelled')", read('supabase/migrations/202606290011_commerce_refunds.sql')], ['content import', "('preview', 'pending', 'importing', 'completed', 'completed_with_errors', 'failed', 'rejected')", read('supabase/migrations/202606210007_content_import_assets.sql')], ['CRM queue', "('pending', 'processing', 'retrying', 'sent', 'failed', 'discarded')", read('supabase/migrations/202606290010_crm_worker_hardening.sql')], ['commission settlement', "('draft', 'pending_review', 'approved', 'paid', 'rejected', 'cancelled')", read('supabase/migrations/202606290009_commission_settlements.sql')], ]; for (const [name, values, source] of statusContracts) { assert.ok(source.includes(values), `${name} state values are part of the frontend compatibility boundary`); } console.log('[PASS] Taro API response, tenant resolution, pagination and state-machine compatibility contract');