Require speech accountability for agency pressure
This commit is contained in:
parent
ea3f8c159d
commit
0851e41cb3
10 changed files with 383 additions and 6 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
228
scripts/smoke-void-rumination-speech-pressure-fixture.ps1
Normal file
228
scripts/smoke-void-rumination-speech-pressure-fixture.ps1
Normal 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
|
||||
}
|
||||
Loading…
Reference in a new issue