Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" $repoRoot = Split-Path -Parent $PSScriptRoot Set-Location -LiteralPath $repoRoot $tempRoot = Join-Path $env:TEMP ("void-rumination-speech-fixture-" + [guid]::NewGuid().ToString("n")) $stateFilePath = Join-Path $tempRoot "void-self-state.cc" $fakeCodexPath = Join-Path $tempRoot "fake-codex-rumination-speech.mjs" $fakeSendPath = Join-Path $tempRoot "fake-send-discord-message.mjs" $cursorOperationPath = Join-Path $tempRoot "cursor.json" $fixtureStatusDir = Join-Path $tempRoot "status" $fixtureLogDir = Join-Path $tempRoot "logs" $statusOperationPath = Join-Path $fixtureStatusDir "moderation-rumination-operations.json" $statusPath = Join-Path $fixtureStatusDir "moderation-rumination.json" New-Item -ItemType Directory -Force -Path $tempRoot | 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 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("The parent runner owns posting and receipts")) { console.error("fixture prompt did not contain the parent-owned speech contract"); process.exit(2); } const operations = [ { operation: "queue_candidate_intervention", intervention: { interventionId: "fixture-parent-owned-speech", kind: "ripe_thought_share", status: "queued", target: { kind: "room", id: "fixture-channel", label: "Fixture channel", }, summary: "Parent-owned speech should close the action loop.", draft: "This is a fixture sentence proving the parent owns the mouth.", deliveryTarget: { mode: "channel", channelId: "fixture-channel-id", replyToMessageId: "fixture-message-id", personaName: "Void", }, priority: 0.9, mustEventuallyShare: false, createdAt: "2026-05-17T03:00:00.000Z", updatedAt: "2026-05-17T03:00:00.000Z", tags: ["fixture"], }, }, { operation: "queue_candidate_intervention", intervention: { interventionId: "fixture-duplicate-same-reply-target", kind: "direct_reply", status: "queued", target: { kind: "room", id: "fixture-channel", label: "Fixture channel", }, summary: "Duplicate same-target candidate should retire when the sibling speaks.", draft: "This duplicate should never be delivered after the same reply target is answered.", deliveryTarget: { mode: "channel", channelId: "fixture-channel-id", replyToMessageId: "fixture-message-id", personaName: "Void", }, priority: 0.7, mustEventuallyShare: false, createdAt: "2026-05-17T03:00:01.000Z", updatedAt: "2026-05-17T03:00:01.000Z", tags: ["fixture", "duplicate"], }, }, { operation: "queue_candidate_intervention", intervention: { interventionId: "fixture-already-answered-reply-target", kind: "direct_reply", status: "queued", target: { kind: "room", id: "fixture-channel", label: "Fixture channel", }, summary: "Already-answered reply target should retire before delivery.", draft: "This should retire because the reply target already has a receipt.", deliveryTarget: { mode: "channel", channelId: "fixture-channel-id", replyToMessageId: "fixture-answered-message-id", personaName: "Void", }, priority: 0.95, mustEventuallyShare: false, createdAt: "2026-05-17T03:00:02.000Z", updatedAt: "2026-05-17T03:00:02.000Z", tags: ["fixture", "already-answered"], }, }, ]; 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 rumination queued one deliverable candidate\n", "utf8"); } process.stdout.write(JSON.stringify({ type: "fixture", operationCount: operations.length }) + "\n"); }); '@ [System.IO.File]::WriteAllText($fakeCodexPath, $fakeCodexSource, [System.Text.UTF8Encoding]::new($false)) $fakeSendSource = @' import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; const statusDir = resolve(process.env.VOID_STATUS_DIR); const contentFileIndex = process.argv.indexOf("--content-file"); const channelIndex = process.argv.indexOf("--channel-id"); const replyIndex = process.argv.indexOf("--reply-to"); const personaIndex = process.argv.indexOf("--persona-name"); if (contentFileIndex < 0 || channelIndex < 0) { console.error("fake sender expected --content-file and --channel-id"); process.exit(2); } const content = readFileSync(resolve(process.argv[contentFileIndex + 1]), "utf8"); const channelId = process.argv[channelIndex + 1]; const replyToMessageId = replyIndex >= 0 ? process.argv[replyIndex + 1] : undefined; const personaName = personaIndex >= 0 ? process.argv[personaIndex + 1] : undefined; const payload = { sentAt: "2026-05-17T03:01:00.000Z", mode: "channel", transport: personaName ? "webhook" : "bot", channelId, replyToMessageId, personaName, contentLength: content.trim().length, chunkCount: 1, preview: content.trim().slice(0, 280), }; mkdirSync(statusDir, { recursive: true }); writeFileSync(resolve(statusDir, "void-last-speech.json"), `${JSON.stringify(payload, null, 2)}\n`, "utf8"); writeFileSync(resolve(statusDir, "void-speech-log.jsonl"), `${JSON.stringify(payload)}\n`, { encoding: "utf8", flag: "a" }); process.stdout.write(JSON.stringify({ ok: true, mode: "channel", channelId }) + "\n"); '@ [System.IO.File]::WriteAllText($fakeSendPath, $fakeSendSource, [System.Text.UTF8Encoding]::new($false)) $cursorOperation = @{ operation = "record_reviewed_messages" lastReviewedMessageId = "fixture-future-cursor" lastReviewedTimestamp = "2099-05-17T00:00:00.000Z" } $cursorOperation | 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 $openCaseOperation = @{ operation = "upsert_open_case" case = @{ sourceMessageId = "fixture-answered-message-id" status = "pending" summary = "Metacrat asked a fixture question, and Void has not answered yet." authorId = "fixture-author" authorName = "Metacrat" channelId = "fixture-channel-id" createdAt = "2026-05-17T02:57:00.000Z" lastTouchedAt = "2026-05-17T02:57:00.000Z" tags = @("fixture") } } $openCaseOperation | 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 $priorReceiptOperation = @{ operation = "record_delivery_receipt" receipt = @{ receiptKey = "fixture-previous-answer" sentAt = "2026-05-17T02:58:00.000Z" mode = "channel" transport = "webhook" channelId = "fixture-channel-id" replyToMessageId = "fixture-answered-message-id" personaName = "Void" preview = "An earlier answer already closed this exact reply target." contentLength = 56 chunkCount = 1 } } $priorReceiptOperation | 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 $previousCodexExecutable = $env:CODEX_EXECUTABLE $previousCodexExecArgs = $env:CODEX_EXEC_ARGS $previousFixtureOutput = $env:VOID_RUMINATION_FIXTURE_OPERATION_OUTPUT $previousStatusDir = $env:VOID_RUMINATION_STATUS_DIR $previousLogDir = $env:VOID_RUMINATION_LOG_DIR $previousSendScript = $env:VOID_SEND_DISCORD_SCRIPT $previousDisableRepoCursorAdvance = $env:VOID_RUMINATION_DISABLE_REPO_CURSOR_ADVANCE try { $env:CODEX_EXECUTABLE = "node" $env:CODEX_EXEC_ARGS = $fakeCodexPath $env:VOID_RUMINATION_FIXTURE_OPERATION_OUTPUT = $statusOperationPath $env:VOID_RUMINATION_STATUS_DIR = $fixtureStatusDir $env:VOID_RUMINATION_LOG_DIR = $fixtureLogDir $env:VOID_SEND_DISCORD_SCRIPT = $fakeSendPath $env:VOID_RUMINATION_DISABLE_REPO_CURSOR_ADVANCE = "1" powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\run-void-moderator-rumination.ps1 -StateFilePath $stateFilePath | Out-Null } finally { $env:CODEX_EXECUTABLE = $previousCodexExecutable $env:CODEX_EXEC_ARGS = $previousCodexExecArgs $env:VOID_RUMINATION_FIXTURE_OPERATION_OUTPUT = $previousFixtureOutput $env:VOID_RUMINATION_STATUS_DIR = $previousStatusDir $env:VOID_RUMINATION_LOG_DIR = $previousLogDir $env:VOID_SEND_DISCORD_SCRIPT = $previousSendScript $env:VOID_RUMINATION_DISABLE_REPO_CURSOR_ADVANCE = $previousDisableRepoCursorAdvance } $status = Get-Content -LiteralPath $statusPath -Raw -Encoding UTF8 | ConvertFrom-Json if ($status.status -ne "ok") { $failureMessage = if ($null -ne $status.PSObject.Properties["failureMessage"]) { [string]$status.failureMessage } else { "(no failure message)" } throw "Speech fixture did not finish ok: $failureMessage" } if ([int]$status.proposedOperationCount -ne 3 -or [int]$status.appliedOperationCount -ne 5 -or [int]$status.deliveredCandidateCount -ne 1) { throw "Speech fixture expected three proposals, five applied operations, and one delivered candidate." } $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); })" $stateFilePath $state = $stateJson | ConvertFrom-Json $candidate = @($state.candidateInterventions.interventions | Where-Object { $_.interventionId -eq "fixture-parent-owned-speech" }) | Select-Object -First 1 $duplicateCandidate = @($state.candidateInterventions.interventions | Where-Object { $_.interventionId -eq "fixture-duplicate-same-reply-target" }) | Select-Object -First 1 $alreadyAnsweredCandidate = @($state.candidateInterventions.interventions | Where-Object { $_.interventionId -eq "fixture-already-answered-reply-target" }) | Select-Object -First 1 $receipt = @( $state.speechReceipts.recentReceipts | Where-Object { $property = $_.PSObject.Properties["candidateInterventionId"] $null -ne $property -and $property.Value -eq "fixture-parent-owned-speech" } ) | Select-Object -First 1 if ($null -ne $candidate) { throw "Speech fixture left the delivered candidate in current state." } if ($null -ne $duplicateCandidate) { throw "Speech fixture left the duplicate same-target candidate in current state." } if ($null -ne $alreadyAnsweredCandidate) { throw "Speech fixture left the already-answered reply target candidate in current state." } if ($receipt.channelId -ne "fixture-channel-id" -or $receipt.replyToMessageId -ne "fixture-message-id") { throw "Speech fixture did not preserve the delivery receipt target." } if ($receipt.candidateInterventionId -ne "fixture-parent-owned-speech") { throw "Speech fixture did not link the delivery receipt back to the spoken candidate." } $answeredCase = @($state.moderationCursor.openCases | Where-Object { $_.sourceMessageId -eq "fixture-answered-message-id" }) | Select-Object -First 1 if ($null -ne $answeredCase) { throw "Speech fixture left an answered open case in current state." } $lastMessage = Get-Content -LiteralPath (Join-Path $fixtureStatusDir "moderation-rumination-last-message.txt") -Raw -Encoding UTF8 if ($lastMessage -notmatch "deliveredCandidates=1") { throw "Speech fixture did not refresh the final rumination message summary." } @{ status = "ok" candidateRetained = $null -ne $candidate staleDuplicateRetained = $null -ne $alreadyAnsweredCandidate receiptCount = @($state.speechReceipts.recentReceipts).Count deliveredCandidateCount = [int]$status.deliveredCandidateCount } | ConvertTo-Json -Compress } finally { Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue }