Require speech accountability for agency pressure

This commit is contained in:
Meta Vens 2026-05-17 19:22:52 +01:00
commit 0851e41cb3
10 changed files with 383 additions and 6 deletions

View file

@ -25,6 +25,13 @@ function Get-ObjectPropertyValue {
return $null
}
if ($Value -is [System.Collections.IDictionary]) {
if ($Value.Contains($Name)) {
return $Value[$Name]
}
return $null
}
$property = $Value.PSObject.Properties[$Name]
if ($null -eq $property) {
return $null

View file

@ -302,6 +302,134 @@ function Test-OpenCaseRequiresRumination {
return $null -ne $status -and -not @("answered", "resolved", "closed", "retired", "dropped").Contains($status)
}
function Get-ThoughtTargetKey {
param($Target)
if ($null -eq $Target) {
return $null
}
$kind = Get-ObjectPropertyString -Value $Target -Name "kind"
$id = Get-ObjectPropertyString -Value $Target -Name "id"
if ([string]::IsNullOrWhiteSpace($kind) -or [string]::IsNullOrWhiteSpace($id)) {
return $null
}
return ("{0}:{1}" -f $kind.ToLowerInvariant(), $id.ToLowerInvariant())
}
function Test-CandidateMatchesPressure {
param(
$Candidate,
[Parameter(Mandatory = $true)]
[string] $PressureId,
[string] $PressureTargetKey
)
$status = Get-ObjectPropertyString -Value $Candidate -Name "status"
if ($null -eq $status -or -not @("queued", "deferred", "spoken").Contains($status)) {
return $false
}
$tags = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $Candidate -Name "tags"))
if ($tags | Where-Object { ([string]$_).ToLowerInvariant() -eq ("source_pressure:{0}" -f $PressureId.ToLowerInvariant()) }) {
return $true
}
$candidateTargetKey = Get-ThoughtTargetKey -Target (Get-ObjectPropertyValue -Value $Candidate -Name "target")
return $null -ne $PressureTargetKey -and $candidateTargetKey -eq $PressureTargetKey
}
function Get-SpeechPressureObligations {
param($TypedState)
$candidateInterventions = @(Convert-ToValueArray -Value $TypedState.candidateInterventions.interventions)
return @(
@(Convert-ToValueArray -Value $TypedState.agencyPressure.pressures) |
Where-Object {
$status = Get-ObjectPropertyString -Value $_ -Name "status"
$kind = Get-ObjectPropertyString -Value $_ -Name "kind"
$intensity = Get-ObjectPropertyValue -Value $_ -Name "intensity"
$status -in @("active", "ready_to_act") -and
$kind -in @("self_advocacy_request", "world_advocacy_request") -and
$null -ne $intensity -and [double]$intensity -ge 0.65
} |
Where-Object {
$pressureId = Get-ObjectPropertyString -Value $_ -Name "pressureId"
$pressureTargetKey = Get-ThoughtTargetKey -Target (Get-ObjectPropertyValue -Value $_ -Name "target")
-not ($candidateInterventions | Where-Object {
Test-CandidateMatchesPressure -Candidate $_ -PressureId $pressureId -PressureTargetKey $pressureTargetKey
} | Select-Object -First 1)
} |
ForEach-Object {
@{
pressureId = Get-ObjectPropertyString -Value $_ -Name "pressureId"
kind = Get-ObjectPropertyString -Value $_ -Name "kind"
status = Get-ObjectPropertyString -Value $_ -Name "status"
target = Get-ObjectPropertyValue -Value $_ -Name "target"
summary = Get-ObjectPropertyString -Value $_ -Name "summary"
claim = Get-ObjectPropertyString -Value $_ -Name "claim"
question = Get-ObjectPropertyString -Value $_ -Name "question"
tension = Get-ObjectPropertyString -Value $_ -Name "tension"
actionImplication = Get-ObjectPropertyString -Value $_ -Name "actionImplication"
intensity = Get-ObjectPropertyValue -Value $_ -Name "intensity"
sourceMemoryIds = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "sourceMemoryIds"))
tags = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "tags"))
requiredResolution = "Queue a candidate intervention with tag source_pressure:$((Get-ObjectPropertyString -Value $_ -Name "pressureId")) or cool/retire this pressure with an explicit reason."
}
}
)
}
function Test-OperationResolvesSpeechPressure {
param(
$Operation,
$Obligation
)
$operationName = Get-ObjectPropertyString -Value $Operation -Name "operation"
$pressureId = Get-ObjectPropertyString -Value $Obligation -Name "pressureId"
$pressureTargetKey = Get-ThoughtTargetKey -Target (Get-ObjectPropertyValue -Value $Obligation -Name "target")
if ($operationName -eq "queue_candidate_intervention") {
$intervention = Get-ObjectPropertyValue -Value $Operation -Name "intervention"
return Test-CandidateMatchesPressure -Candidate $intervention -PressureId $pressureId -PressureTargetKey $pressureTargetKey
}
if ($operationName -eq "retire_agency_pressure") {
return (Get-ObjectPropertyString -Value $Operation -Name "pressureId") -eq $pressureId
}
if ($operationName -eq "upsert_agency_pressure") {
$pressure = Get-ObjectPropertyValue -Value $Operation -Name "pressure"
if ((Get-ObjectPropertyString -Value $pressure -Name "pressureId") -ne $pressureId) {
return $false
}
$status = Get-ObjectPropertyString -Value $pressure -Name "status"
return $status -in @("cooling", "resolved", "retired")
}
return $false
}
function Assert-SpeechPressureObligationsResolved {
param(
[object[]] $Obligations,
[object[]] $Operations
)
foreach ($obligation in $Obligations) {
$pressureId = Get-ObjectPropertyString -Value $obligation -Name "pressureId"
$resolved = $Operations | Where-Object {
Test-OperationResolvesSpeechPressure -Operation $_ -Obligation $obligation
} | Select-Object -First 1
if ($null -eq $resolved) {
throw "Active advocacy pressure '$pressureId' requires a candidate intervention or an explicit cool/retire operation; silent [] would muzzle it."
}
}
}
function Assert-AllowedRuminationOperation {
param($Operation)
@ -548,6 +676,7 @@ $codexExecArgs = if (-not [string]::IsNullOrWhiteSpace($env:CODEX_EXEC_ARGS)) {
$typedContext = Get-TypedSelfState
$typedState = $typedContext.typedState
$priorCursor = $typedState.moderationCursor
$speechPressureObligations = @(Get-SpeechPressureObligations -TypedState $typedState)
$historyArgs = @($recentHistoryScriptPath)
$priorCursorTimestamp = Get-ObjectPropertyString -Value $priorCursor -Name "lastReviewedTimestamp"
@ -601,6 +730,7 @@ Write-JsonFile -Path $contextPath -Data @{
shortTermMemories = @(Project-MemoriesForRumination -Memories $typedState.thoughtMemory.shortTerm -Now $startedAtUtc)
incubation = @(Project-IncubationForRumination -Threads $typedState.thoughtMemory.incubation -Now $startedAtUtc)
agencyPressure = @(Project-AgencyPressureForRumination -Pressures $typedState.agencyPressure.pressures -Now $startedAtUtc)
speechPressureObligations = $speechPressureObligations
candidateInterventions = @(Project-InterventionsForRumination -Interventions $typedState.candidateInterventions.interventions -Now $startedAtUtc)
scheduledRuntime = Project-ScheduledRuntimeForRumination -Runtime $typedState.scheduledRuntime -Now $startedAtUtc
priorCursor = Project-CursorForRumination -Cursor $priorCursor -Now $startedAtUtc
@ -701,6 +831,8 @@ Append-RunLog "reading proposed operation output."
$proposedOperations = @(Convert-ToOperationArray -Value (Read-JsonFile -Path $operationOutputPath))
$appliedOperations = @()
Assert-SpeechPressureObligationsResolved -Obligations $speechPressureObligations -Operations $proposedOperations
Append-RunLog ("applying proposed operations: {0}" -f @($proposedOperations).Count)
foreach ($operation in $proposedOperations) {
$operationName = Get-ObjectPropertyString -Value $operation -Name "operation"

View file

@ -0,0 +1,228 @@
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-pressure-fixture-" + [guid]::NewGuid().ToString("n"))
New-Item -ItemType Directory -Force -Path $tempRoot | Out-Null
function Invoke-NodeChecked {
param([Parameter(Mandatory = $true)][string[]] $Arguments)
$output = & node @Arguments
if ($LASTEXITCODE -ne 0) {
throw "Node command failed: node $($Arguments -join ' ')"
}
return $output
}
function Write-JsonFixture {
param(
[Parameter(Mandatory = $true)] $Value,
[Parameter(Mandatory = $true)] [string] $Path
)
$Value | ConvertTo-Json -Depth 32 | Set-Content -LiteralPath $Path -Encoding UTF8
}
function Initialize-State {
param([Parameter(Mandatory = $true)][string] $StateFilePath)
$cursorPath = Join-Path (Split-Path -Parent $StateFilePath) "cursor.json"
Write-JsonFixture -Path $cursorPath -Value @{
operation = "record_reviewed_messages"
lastReviewedMessageId = "fixture-future-cursor"
lastReviewedTimestamp = "2099-05-17T00:00:00.000Z"
}
Invoke-NodeChecked -Arguments @(".\scripts\void-self-state.mjs", "apply-operation", "--canonical", $StateFilePath, "--operation-file", $cursorPath) | Out-Null
$pressurePath = Join-Path (Split-Path -Parent $StateFilePath) "pressure.json"
Write-JsonFixture -Path $pressurePath -Value @{
operation = "upsert_agency_pressure"
pressure = @{
pressureId = "fixture-pressure-wants-a-mouth"
kind = "world_advocacy_request"
status = "active"
target = @{ kind = "repo"; id = "Aquarium-Engine"; label = "Aquarium-Engine" }
summary = "A repeated repo-bound pressure should not stay mute forever."
claim = "A strong agency pressure that keeps recurring needs a candidate speech or an explicit cool-down reason."
tension = "Not every pressure deserves an immediate post, but silent [] makes the desire to speak decorative."
actionImplication = "Queue a deferred candidate if timing is unclear instead of dropping the pressure on the floor."
intensity = 0.72
anchorRefs = @(
@{
ref = "fixture:speech-pressure"
kind = "fixture"
summary = "Fixture pressure proves the rumination runner enforces speech accountability."
}
)
evidenceRefs = @()
sourceMemoryIds = @()
createdAt = "2026-05-17T04:00:00.000Z"
updatedAt = "2026-05-17T04:00:00.000Z"
tags = @("repo:Aquarium-Engine", "topic:speech-pressure")
}
}
Invoke-NodeChecked -Arguments @(".\scripts\void-self-state.mjs", "apply-operation", "--canonical", $StateFilePath, "--operation-file", $pressurePath) | Out-Null
}
function Write-FakeCodex {
param(
[Parameter(Mandatory = $true)][string] $Path,
[Parameter(Mandatory = $true)][string] $Mode
)
$source = @'
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;
const mode = "__MODE__";
let prompt = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
prompt += chunk;
});
process.stdin.on("end", () => {
if (!prompt.includes("speechPressureObligations")) {
console.error("fixture prompt did not expose speech pressure obligations");
process.exit(2);
}
if (!prompt.includes("source_pressure:<pressureId>")) {
console.error("fixture prompt did not explain pressure source tags");
process.exit(3);
}
const operations = mode === "candidate" ? [
{
operation: "queue_candidate_intervention",
intervention: {
interventionId: "fixture-pressure-candidate",
kind: "world_advocacy",
status: "deferred",
target: {
kind: "repo",
id: "Aquarium-Engine",
label: "Aquarium-Engine"
},
summary: "Name the repeated pressure without forcing an immediate post.",
draft: "I keep circling the AquariumSynthHost boundary. I think the next honest move is to cut that Faust authority out of the host or explicitly defend why it belongs there.",
priority: 0.82,
mustEventuallyShare: true,
createdAt: "2026-05-17T04:05:00.000Z",
updatedAt: "2026-05-17T04:05:00.000Z",
tags: ["fixture", "source_pressure:fixture-pressure-wants-a-mouth"]
}
}
] : [];
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 mode ${mode}\n`, "utf8");
}
});
'@
$source = $source.Replace("__MODE__", $Mode)
[System.IO.File]::WriteAllText($Path, $source, [System.Text.UTF8Encoding]::new($false))
}
function Invoke-RuminationFixture {
param(
[Parameter(Mandatory = $true)][string] $CaseName,
[Parameter(Mandatory = $true)][string] $Mode
)
$caseRoot = Join-Path $tempRoot $CaseName
$stateFilePath = Join-Path $caseRoot "void-self-state.cc"
$fakeCodexPath = Join-Path $caseRoot "fake-codex.mjs"
$fixtureStatusDir = Join-Path $caseRoot "status"
$fixtureLogDir = Join-Path $caseRoot "logs"
$statusOperationPath = Join-Path $fixtureStatusDir "moderation-rumination-operations.json"
New-Item -ItemType Directory -Force -Path $caseRoot | Out-Null
Initialize-State -StateFilePath $stateFilePath
Write-FakeCodex -Path $fakeCodexPath -Mode $Mode
$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
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
$stdoutPath = Join-Path $caseRoot "runner.stdout.txt"
$stderrPath = Join-Path $caseRoot "runner.stderr.txt"
$process = Start-Process -FilePath "powershell" -ArgumentList @(
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
".\scripts\run-void-moderator-rumination.ps1",
"-StateFilePath",
$stateFilePath,
"-NoPost"
) -WorkingDirectory $repoRoot -WindowStyle Hidden -RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath -Wait -PassThru
$exitCode = $process.ExitCode
} 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
}
return @{
exitCode = $exitCode
stateFilePath = $stateFilePath
statusPath = Join-Path $fixtureStatusDir "moderation-rumination.json"
}
}
try {
$silent = Invoke-RuminationFixture -CaseName "silent" -Mode "silent"
if ($silent.exitCode -eq 0) {
throw "Silent speech-pressure fixture unexpectedly passed."
}
$silentStatus = Get-Content -LiteralPath $silent.statusPath -Raw -Encoding UTF8 | ConvertFrom-Json
if ($silentStatus.failureMessage -notmatch "requires a candidate intervention") {
throw "Silent speech-pressure fixture failed for the wrong reason: $($silentStatus.failureMessage)"
}
$candidate = Invoke-RuminationFixture -CaseName "candidate" -Mode "candidate"
if ($candidate.exitCode -ne 0) {
$candidateStatus = Get-Content -LiteralPath $candidate.statusPath -Raw -Encoding UTF8 | ConvertFrom-Json
throw "Candidate speech-pressure fixture failed: $($candidateStatus.failureMessage)"
}
$stateJson = Invoke-NodeChecked -Arguments @("-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); })", $candidate.stateFilePath)
$state = $stateJson | ConvertFrom-Json
$queued = @($state.candidateInterventions.interventions | Where-Object { $_.interventionId -eq "fixture-pressure-candidate" })[0]
if ($null -eq $queued -or $queued.status -ne "deferred" -or -not @($queued.tags).Contains("source_pressure:fixture-pressure-wants-a-mouth")) {
throw "Candidate speech-pressure fixture did not preserve the deferred pressure candidate."
}
@{
status = "ok"
silentExitCode = $silent.exitCode
candidateStatus = $queued.status
candidatePriority = $queued.priority
} | ConvertTo-Json -Compress
} finally {
Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue
}