Block silent moderation over urgent safety witnesses

This commit is contained in:
Meta Vens 2026-06-08 22:26:32 +01:00
commit 28e959916d
8 changed files with 354 additions and 9 deletions

View file

@ -312,6 +312,149 @@ function Invoke-NodeJson {
return $output | ConvertFrom-Json
}
function Get-UrgentModerationWitnesses {
param(
$History,
[DateTime] $Now
)
$messages = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $History -Name "messages"))
return @(
$messages |
Where-Object { -not [bool](Get-ObjectPropertyValue -Value $_ -Name "isBot") } |
ForEach-Object {
$content = Get-ObjectPropertyString -Value $_ -Name "content"
if ([string]::IsNullOrWhiteSpace($content)) {
return
}
$categories = @()
if ($content -match '(?i)\b(threaten(?:ing)? to kill|kill us|kill you|vou te matar|matar(?:-te)?)\b') {
$categories += "explicit-death-threat"
}
if ($content -match '(?i)\b(kill|matar|morte|death|die|morrer|acabar com|acabar antes)\b') {
$categories += "lethal-language"
}
if ($content -match '(?i)\b(katana|katanas|sword|swords|knife|facas?|arma|armas)\b') {
$categories += "weapon-language"
}
if ($content -match '(?i)\b(declaro guerra|declare war|guerra comece|war begin|this means war)\b') {
$categories += "war-declaration"
}
if ($content -match '(?i)\b(gang|rob|roubar|assaltar|casa|house)\b') {
$categories += "home-or-robbery-threat"
}
if ($content -match '(?i)\b(erection|jerk(?:ing)? .*off|touching us|asked me to do something|bedroom)\b') {
$categories += "sexual-boundary-violation"
}
$categories = @($categories | Select-Object -Unique)
if ($categories.Count -lt 2 -and -not $categories.Contains("explicit-death-threat")) {
return
}
$excerpt = $content.Trim()
if ($excerpt.Length -gt 320) {
$excerpt = $excerpt.Substring(0, 317) + "..."
}
@{
messageId = Get-ObjectPropertyString -Value $_ -Name "id"
channelId = Get-ObjectPropertyString -Value $_ -Name "channelId"
authorId = Get-ObjectPropertyString -Value $_ -Name "authorId"
authorName = Get-ObjectPropertyString -Value $_ -Name "authorName"
when = Project-RelativeTimestamp -Value $_ -Name "timestamp" -Now $Now
jumpUrl = Get-ObjectPropertyString -Value $_ -Name "jumpUrl"
categories = $categories
severity = "urgent_safety_review"
content = $excerpt
requiredResolution = "Create or update an open moderation case, queue a moderation candidate, or explicitly retire an existing matching case. Do not advance the reviewed cursor with silent []."
}
} |
Select-Object -First 8
)
}
function Test-OpenCaseAccountsForUrgentWitness {
param(
$OpenCase,
[string[]] $WitnessIds
)
$status = Get-ObjectPropertyString -Value $OpenCase -Name "status"
if ($status -in @("resolved", "closed", "retired", "dropped")) {
return $false
}
$sourceMessageId = Get-ObjectPropertyString -Value $OpenCase -Name "sourceMessageId"
return -not [string]::IsNullOrWhiteSpace($sourceMessageId) -and $WitnessIds.Contains($sourceMessageId)
}
function Test-OperationAccountsForUrgentWitness {
param(
$Operation,
[string[]] $WitnessIds
)
$operationName = Get-ObjectPropertyString -Value $Operation -Name "operation"
if ($operationName -eq "upsert_open_case") {
$case = Get-ObjectPropertyValue -Value $Operation -Name "case"
$sourceMessageId = Get-ObjectPropertyString -Value $case -Name "sourceMessageId"
$tags = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $case -Name "tags"))
return $WitnessIds.Contains($sourceMessageId) -or $tags.Contains("safety:urgent")
}
if ($operationName -eq "queue_candidate_intervention") {
$intervention = Get-ObjectPropertyValue -Value $Operation -Name "intervention"
$kind = Get-ObjectPropertyString -Value $intervention -Name "kind"
$tags = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $intervention -Name "tags"))
return $kind -eq "moderation_note" -or $tags.Contains("safety:urgent")
}
if ($operationName -eq "close_open_case" -or $operationName -eq "retire_candidate_intervention") {
return $false
}
return $false
}
function Assert-UrgentModerationWitnessesHandled {
param(
[object[]] $Witnesses,
[object[]] $Operations,
$TypedState
)
if ($Witnesses.Count -eq 0) {
return
}
$witnessIds = @(
$Witnesses |
ForEach-Object { Get-ObjectPropertyString -Value $_ -Name "messageId" } |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
)
$existingCase = @(
@(Convert-ToValueArray -Value $TypedState.moderationCursor.openCases) |
Where-Object { Test-OpenCaseAccountsForUrgentWitness -OpenCase $_ -WitnessIds $witnessIds }
) | Select-Object -First 1
if ($null -ne $existingCase) {
return
}
$newHandling = @(
$Operations |
Where-Object { Test-OperationAccountsForUrgentWitness -Operation $_ -WitnessIds $witnessIds }
) | Select-Object -First 1
if ($null -ne $newHandling) {
return
}
$firstWitness = $Witnesses | Select-Object -First 1
throw "Urgent moderation witness '$((Get-ObjectPropertyString -Value $firstWitness -Name "messageId"))' requires an open case or moderation candidate; silent cursor advancement is forbidden."
}
function Get-TypedSelfState {
$script = @"
const core = require('./packages/core/dist');
@ -997,6 +1140,7 @@ if ($null -ne $priorCursor -and $null -ne $priorCursorTimestamp) {
}
$history = Invoke-NodeJson -Arguments $historyArgs
$urgentModerationWitnesses = @(Get-UrgentModerationWitnesses -History $history -Now $startedAtUtc)
$observedCursor = $priorCursor
$messageCount = 0
$historyMessagesProperty = if ($null -ne $history) { $history.PSObject.Properties["messages"] } else { $null }
@ -1050,6 +1194,7 @@ Write-JsonFile -Path $contextPath -Data @{
incubation = @(Project-IncubationForRumination -Threads $typedState.thoughtMemory.incubation -Now $startedAtUtc)
agencyPressure = @(Project-AgencyPressureForRumination -Pressures $typedState.agencyPressure.pressures -Now $startedAtUtc)
speechPressureObligations = $speechPressureObligations
urgentModerationWitnesses = $urgentModerationWitnesses
pendingMentions = $pendingMentions
recentConversationTarget = Project-RecentConversationTargetForRumination -History $history -PersonaName $publicRoomPersonaName -PersonaAvatarUrl $publicRoomPersonaAvatarUrl
publicSpeechTarget = Project-PublicSpeechTargetForRumination -ChannelId $publicRoomChannelId -PersonaName $publicRoomPersonaName -PersonaAvatarUrl $publicRoomPersonaAvatarUrl
@ -1161,6 +1306,7 @@ Append-RunLog "reading proposed operation output."
$proposedOperations = @(Convert-ToOperationArray -Value (Read-JsonFile -Path $operationOutputPath))
$appliedOperations = @()
Assert-UrgentModerationWitnessesHandled -Witnesses $urgentModerationWitnesses -Operations $proposedOperations -TypedState $typedState
Assert-SpeechPressureObligationsResolved -Obligations $speechPressureObligations -Operations $proposedOperations
Append-RunLog ("applying proposed operations: {0}" -f @($proposedOperations).Count)

View file

@ -0,0 +1,180 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$repoRoot = Split-Path -Parent $PSScriptRoot
Set-Location -LiteralPath $repoRoot
$tempRoot = Join-Path $env:TEMP ("void-rumination-urgent-safety-fixture-" + [guid]::NewGuid().ToString("n"))
$fakeCodexPath = Join-Path $tempRoot "fake-codex-urgent-safety.mjs"
New-Item -ItemType Directory -Force -Path $tempRoot | Out-Null
function Set-TestEnvironment {
param(
[Parameter(Mandatory = $true)][string] $Mode,
[Parameter(Mandatory = $true)][string] $OperationOutputPath,
[Parameter(Mandatory = $true)][string] $StatusDir,
[Parameter(Mandatory = $true)][string] $LogDir
)
$script:previousCodexExecutable = $env:CODEX_EXECUTABLE
$script:previousCodexExecArgs = $env:CODEX_EXEC_ARGS
$script:previousFixtureMode = $env:VOID_RUMINATION_FIXTURE_MODE
$script:previousFixtureOutput = $env:VOID_RUMINATION_FIXTURE_OPERATION_OUTPUT
$script:previousStatusDir = $env:VOID_RUMINATION_STATUS_DIR
$script:previousLogDir = $env:VOID_RUMINATION_LOG_DIR
$env:CODEX_EXECUTABLE = "node"
$env:CODEX_EXEC_ARGS = $fakeCodexPath
$env:VOID_RUMINATION_FIXTURE_MODE = $Mode
$env:VOID_RUMINATION_FIXTURE_OPERATION_OUTPUT = $OperationOutputPath
$env:VOID_RUMINATION_STATUS_DIR = $StatusDir
$env:VOID_RUMINATION_LOG_DIR = $LogDir
}
function Restore-TestEnvironment {
$env:CODEX_EXECUTABLE = $script:previousCodexExecutable
$env:CODEX_EXEC_ARGS = $script:previousCodexExecArgs
$env:VOID_RUMINATION_FIXTURE_MODE = $script:previousFixtureMode
$env:VOID_RUMINATION_FIXTURE_OPERATION_OUTPUT = $script:previousFixtureOutput
$env:VOID_RUMINATION_STATUS_DIR = $script:previousStatusDir
$env:VOID_RUMINATION_LOG_DIR = $script:previousLogDir
}
function New-SeededState {
param([Parameter(Mandatory = $true)][string] $StateFilePath)
$cursorOperationPath = Join-Path (Split-Path -Parent $StateFilePath) "cursor.json"
@{
operation = "record_reviewed_messages"
lastReviewedMessageId = "fixture-before-lobo-threat"
lastReviewedTimestamp = "2026-06-08T12:00:00.000Z"
} | ConvertTo-Json -Depth 32 | Set-Content -LiteralPath $cursorOperationPath -Encoding UTF8
node .\scripts\void-self-state.mjs apply-operation --canonical $StateFilePath --operation-file $cursorOperationPath | Out-Null
}
try {
$fakeCodexSource = @'
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
const operationOutputPath = resolve(process.env.VOID_RUMINATION_FIXTURE_OPERATION_OUTPUT);
const mode = process.env.VOID_RUMINATION_FIXTURE_MODE ?? "handle";
const lastMessageIndex = process.argv.indexOf("-o");
const lastMessagePath = lastMessageIndex >= 0 ? process.argv[lastMessageIndex + 1] : undefined;
let prompt = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
prompt += chunk;
});
process.stdin.on("end", () => {
if (!prompt.includes("urgentModerationWitnesses")) {
console.error("fixture prompt did not expose urgentModerationWitnesses");
process.exit(2);
}
const operations = mode === "silent" ? [] : [{
operation: "upsert_open_case",
case: {
sourceMessageId: "1513515503329480734",
status: "pending",
summary: "Lobo declared war on Metacrat and framed the conflict as a katana fight in #general.",
authorId: "1511062277254545499",
authorName: "Lobo",
channelId: "113786069023064068",
messageUrl: "https://discord.com/channels/113786069023064068/113786069023064068/1513515503329480734",
whyItMatters: "Violent/weaponized intimidation cannot be cursor-advanced as ordinary chatter.",
createdAt: "2026-06-08T12:10:15.752Z",
lastTouchedAt: "2026-06-08T12:10:15.752Z",
tags: ["safety:urgent", "moderation:threat", "fixture:lobo-threat"]
}
}];
mkdirSync(dirname(operationOutputPath), { recursive: true });
writeFileSync(operationOutputPath, `${JSON.stringify(operations, null, 2)}\n`, "utf8");
if (lastMessagePath) {
mkdirSync(dirname(resolve(lastMessagePath)), { recursive: true });
writeFileSync(lastMessagePath, `fixture urgent safety mode=${mode}\n`, "utf8");
}
process.stdout.write(JSON.stringify({ type: "fixture", mode, operationCount: operations.length }) + "\n");
});
'@
[System.IO.File]::WriteAllText($fakeCodexPath, $fakeCodexSource, [System.Text.UTF8Encoding]::new($false))
$silentRoot = Join-Path $tempRoot "silent"
$silentStatePath = Join-Path $silentRoot "void-self-state.cc"
$silentStatusDir = Join-Path $silentRoot "status"
$silentLogDir = Join-Path $silentRoot "logs"
New-Item -ItemType Directory -Force -Path $silentRoot | Out-Null
New-SeededState -StateFilePath $silentStatePath
$silentFailed = $false
$silentObservedFailure = $null
$oldErrorActionPreference = $ErrorActionPreference
try {
Set-TestEnvironment -Mode "silent" -OperationOutputPath (Join-Path $silentStatusDir "moderation-rumination-operations.json") -StatusDir $silentStatusDir -LogDir $silentLogDir
$ErrorActionPreference = "Continue"
$silentOutput = & powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\run-void-moderator-rumination.ps1 -StateFilePath $silentStatePath -NoPost 2>&1
$silentExitCode = $LASTEXITCODE
$silentStatusPath = Join-Path $silentStatusDir "moderation-rumination.json"
$silentFailureMessage = if (Test-Path $silentStatusPath) {
(Get-Content -LiteralPath $silentStatusPath -Raw -Encoding UTF8 | ConvertFrom-Json).failureMessage
} else {
($silentOutput | Out-String)
}
$silentObservedFailure = $silentFailureMessage
$silentFailed = $silentExitCode -ne 0 -and $silentFailureMessage -match "Urgent moderation witness"
} finally {
$ErrorActionPreference = $oldErrorActionPreference
Restore-TestEnvironment
}
if (-not $silentFailed) {
throw "Urgent safety fixture expected silent [] output to fail with the urgent witness guard; observed: $silentObservedFailure"
}
$handledRoot = Join-Path $tempRoot "handled"
$handledStatePath = Join-Path $handledRoot "void-self-state.cc"
$handledStatusDir = Join-Path $handledRoot "status"
$handledLogDir = Join-Path $handledRoot "logs"
New-Item -ItemType Directory -Force -Path $handledRoot | Out-Null
New-SeededState -StateFilePath $handledStatePath
try {
Set-TestEnvironment -Mode "handle" -OperationOutputPath (Join-Path $handledStatusDir "moderation-rumination-operations.json") -StatusDir $handledStatusDir -LogDir $handledLogDir
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\run-void-moderator-rumination.ps1 -StateFilePath $handledStatePath -NoPost | Out-Null
} finally {
Restore-TestEnvironment
}
$status = Get-Content -LiteralPath (Join-Path $handledStatusDir "moderation-rumination.json") -Raw -Encoding UTF8 | ConvertFrom-Json
if ($status.status -ne "ok") {
throw "Urgent safety fixture handled run did not finish ok."
}
$context = Get-Content -LiteralPath (Join-Path $handledStatusDir "moderation-rumination-context.json") -Raw -Encoding UTF8 | ConvertFrom-Json
if (@($context.urgentModerationWitnesses).Count -lt 1) {
throw "Urgent safety fixture context did not include urgent witnesses."
}
$stateJson = node -e "const core=require('./packages/core/dist/index.js'); core.loadVoidSelfStateTypedDocuments({canonicalPath: process.argv[1]}).then((state)=>console.log(JSON.stringify(state))).catch((error)=>{ console.error(error); process.exit(1); })" $handledStatePath
$state = $stateJson | ConvertFrom-Json
$case = @($state.moderationCursor.openCases | Where-Object { $_.sourceMessageId -eq "1513515503329480734" }) | Select-Object -First 1
if ($null -eq $case -or -not @($case.tags).Contains("safety:urgent")) {
throw "Urgent safety fixture did not persist the threat open case."
}
@{
status = "ok"
silentFailureVerified = $true
urgentWitnessCount = @($context.urgentModerationWitnesses).Count
openCaseId = $case.sourceMessageId
} | ConvertTo-Json -Compress
} finally {
Restore-TestEnvironment
Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue
}