feat(release): prepare 1.1.0 candidate

This commit is contained in:
Yun Chan 2026-08-29 18:33:45 +09:00
parent 5a34f66981
commit 5205dcdfa9
736 changed files with 115667 additions and 12203 deletions

View file

@ -0,0 +1,724 @@
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()
}