Block silent moderation over urgent safety witnesses
This commit is contained in:
parent
1150823e2a
commit
28e959916d
8 changed files with 354 additions and 9 deletions
|
|
@ -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)
|
||||
|
|
|
|||
180
scripts/smoke-void-rumination-urgent-safety-fixture.ps1
Normal file
180
scripts/smoke-void-rumination-urgent-safety-fixture.ps1
Normal 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
|
||||
}
|
||||
Loading…
Reference in a new issue