diff --git a/packages/core/src/void-self-state-service.ts b/packages/core/src/void-self-state-service.ts index cced499..e11e0c3 100644 --- a/packages/core/src/void-self-state-service.ts +++ b/packages/core/src/void-self-state-service.ts @@ -245,11 +245,14 @@ function normalizeTypedStateForWrite( } function pruneRetiredTypedState(state: VoidSelfStateTypedProjection): void { + state.moderationCursor.openCases = state.moderationCursor.openCases.filter((entry) => + !isTerminalCaseStatus(entry.status) + ); state.thoughtMemory.shortTerm = state.thoughtMemory.shortTerm.filter(isNotRetiredEntry); state.thoughtMemory.memories = state.thoughtMemory.memories.filter(isNotRetiredEntry); state.thoughtMemory.incubation = state.thoughtMemory.incubation.filter(isNotRetiredEntry); state.agencyPressure.pressures = state.agencyPressure.pressures.filter(isNotRetiredEntry); - state.candidateInterventions.interventions = state.candidateInterventions.interventions.filter(isNotRetiredEntry); + state.candidateInterventions.interventions = state.candidateInterventions.interventions.filter(isLiveCandidateIntervention); state.faceAffect.needs = state.faceAffect.needs.filter(isNotRetiredEntry); state.faceAffect.socialBonds = state.faceAffect.socialBonds.filter(isNotRetiredEntry); state.faceAffect.statusReads = state.faceAffect.statusReads.filter(isNotRetiredEntry); @@ -259,6 +262,10 @@ function isNotRetiredEntry(entry: { status?: string; retiredAt?: string }): bool return entry.status !== "retired" && !entry.retiredAt; } +function isLiveCandidateIntervention(entry: { status?: string; retiredAt?: string }): boolean { + return (entry.status === "queued" || entry.status === "deferred") && !entry.retiredAt; +} + function clampFutureTimestampFields>( value: T, observedAt: string, diff --git a/scripts/run-void-moderator-rumination.ps1 b/scripts/run-void-moderator-rumination.ps1 index c3175f2..45e49d8 100644 --- a/scripts/run-void-moderator-rumination.ps1 +++ b/scripts/run-void-moderator-rumination.ps1 @@ -395,7 +395,7 @@ function Test-CandidateMatchesPressure { [Parameter(Mandatory = $true)] [string] $PressureId, [string] $PressureTargetKey, - [string[]] $AllowedStatuses = @("queued", "spoken") + [string[]] $AllowedStatuses = @("queued", "deferred") ) $status = Get-ObjectPropertyString -Value $Candidate -Name "status" @@ -511,7 +511,7 @@ function Get-SpeechPressureObligations { $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 -AllowedStatuses @("queued") + Test-CandidateMatchesPressure -Candidate $_ -PressureId $pressureId -PressureTargetKey $pressureTargetKey -AllowedStatuses @("queued", "deferred") } | Select-Object -First 1) } | ForEach-Object { @@ -845,17 +845,17 @@ function Assert-SpokenCandidateApplied { $candidate = @( @(Convert-ToValueArray -Value $state.candidateInterventions.interventions) | Where-Object { (Get-ObjectPropertyString -Value $_ -Name "interventionId") -eq $InterventionId } - )[0] + ) | Select-Object -First 1 $receipt = @( @(Convert-ToValueArray -Value $state.speechReceipts.recentReceipts) | Where-Object { (Get-ObjectPropertyString -Value $_ -Name "receiptKey") -eq $ReceiptKey } - )[0] + ) | Select-Object -First 1 - if ($null -eq $candidate -or (Get-ObjectPropertyString -Value $candidate -Name "status") -ne "spoken") { - throw "Delivery receipt '$ReceiptKey' was recorded without retiring candidate '$InterventionId' as spoken." + if ($null -ne $candidate) { + throw "Delivery receipt '$ReceiptKey' was recorded but terminal candidate '$InterventionId' stayed in current state." } if ($null -eq $receipt -or (Get-ObjectPropertyString -Value $receipt -Name "candidateInterventionId") -ne $InterventionId) { - throw "Candidate '$InterventionId' was marked spoken without a linked delivery receipt '$ReceiptKey'." + throw "Candidate '$InterventionId' was delivered without a linked delivery receipt '$ReceiptKey'." } } diff --git a/scripts/smoke-void-memory-lifecycle-fixture.ps1 b/scripts/smoke-void-memory-lifecycle-fixture.ps1 index cf3b13d..0dcedc1 100644 --- a/scripts/smoke-void-memory-lifecycle-fixture.ps1 +++ b/scripts/smoke-void-memory-lifecycle-fixture.ps1 @@ -139,8 +139,8 @@ try { if ($activeMemories.Count -ne 1 -or $identityMemory.memoryId -ne "identity-memory-plasticity") { throw "Memory lifecycle fixture expected one active crystallized identity memory." } - if ($retiredMemories.Count -lt 2) { - throw "Memory lifecycle fixture expected superseded durable memories to be retired." + if ($retiredMemories.Count -ne 0) { + throw "Memory lifecycle fixture left superseded durable memories in current state." } if ($null -eq $value) { throw "Memory lifecycle fixture did not crystallize a self-profile value." @@ -149,7 +149,7 @@ try { @{ status = "ok" activeMemoryCount = $activeMemories.Count - retiredMemoryCount = $retiredMemories.Count + retainedRetiredMemoryCount = $retiredMemories.Count value = $value.label } | ConvertTo-Json -Compress } finally { diff --git a/scripts/smoke-void-rumination-speech-fixture.ps1 b/scripts/smoke-void-rumination-speech-fixture.ps1 index 116ceeb..db8e43d 100644 --- a/scripts/smoke-void-rumination-speech-fixture.ps1 +++ b/scripts/smoke-void-rumination-speech-fixture.ps1 @@ -242,7 +242,8 @@ process.stdout.write(JSON.stringify({ ok: true, mode: "channel", channelId }) + $status = Get-Content -LiteralPath $statusPath -Raw -Encoding UTF8 | ConvertFrom-Json if ($status.status -ne "ok") { - throw "Speech fixture did not finish 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." @@ -250,25 +251,25 @@ process.stdout.write(JSON.stringify({ ok: true, mode: "channel", channelId }) + $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" })[0] - $duplicateCandidate = @($state.candidateInterventions.interventions | Where-Object { $_.interventionId -eq "fixture-duplicate-same-reply-target" })[0] - $alreadyAnsweredCandidate = @($state.candidateInterventions.interventions | Where-Object { $_.interventionId -eq "fixture-already-answered-reply-target" })[0] + $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" } - )[0] + ) | Select-Object -First 1 - if ($candidate.status -ne "spoken") { - throw "Speech fixture did not mark the candidate spoken." + if ($null -ne $candidate) { + throw "Speech fixture left the delivered candidate in current state." } - if ($duplicateCandidate.status -ne "retired") { - throw "Speech fixture did not retire the duplicate same-target candidate." + if ($null -ne $duplicateCandidate) { + throw "Speech fixture left the duplicate same-target candidate in current state." } - if ($alreadyAnsweredCandidate.status -ne "retired") { - throw "Speech fixture did not retire the already-answered reply target candidate." + 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." @@ -276,9 +277,9 @@ process.stdout.write(JSON.stringify({ ok: true, mode: "channel", channelId }) + 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" })[0] - if ($answeredCase.status -ne "answered" -or $answeredCase.summary -match "not answered") { - throw "Speech fixture did not normalize the answered open-case summary." + $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") { @@ -287,8 +288,8 @@ process.stdout.write(JSON.stringify({ ok: true, mode: "channel", channelId }) + @{ status = "ok" - candidateStatus = $candidate.status - staleDuplicateStatus = $alreadyAnsweredCandidate.status + candidateRetained = $null -ne $candidate + staleDuplicateRetained = $null -ne $alreadyAnsweredCandidate receiptCount = @($state.speechReceipts.recentReceipts).Count deliveredCandidateCount = [int]$status.deliveredCandidateCount } | ConvertTo-Json -Compress diff --git a/scripts/smoke-void-rumination-speech-pressure-fixture.ps1 b/scripts/smoke-void-rumination-speech-pressure-fixture.ps1 index 586cc9b..77b64e2 100644 --- a/scripts/smoke-void-rumination-speech-pressure-fixture.ps1 +++ b/scripts/smoke-void-rumination-speech-pressure-fixture.ps1 @@ -269,8 +269,8 @@ try { $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 "queued" -or -not @($queued.tags).Contains("source_pressure:fixture-pressure-wants-a-mouth")) { - throw "Candidate speech-pressure fixture did not preserve the queued pressure candidate." + 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 live deferred pressure candidate." } @{ diff --git a/state/evidence.jsonl b/state/evidence.jsonl index c2959e0..5d85f3f 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -282,3 +282,4 @@ {"ts":"2026-05-22T19:12:12Z","type":"fix","status":"ok","note":"Fixed Void self-state write semantics so retired typed entries are pruned from current documents instead of retained as tombstones. Repaired Mimir's .cc: file shrank from about 54KB to 27KB, retains 8 global typed documents, has zero retired entries in typed arrays, and raw bytes no longer contain LocalCastBridge/wet-voice/witness/canary residue."} {"ts":"2026-05-22T19:23:18Z","type":"maintenance","status":"ok","note":"Ran swarm-wide .cc hygiene after Mimir exposed retained tombstones. Repo Face state files now repair through pruned writes: all registered repo Faces retain 8 global typed documents with zero retired entries; collapse loop terms LocalCastBridge/wet-voice/canary were removed from active repo Face .cc state except Nibu's legitimate Aetheria witness vocabulary. Void's own self-state was also repaired, shrinking from about 171KB to 135KB and removing 5 retired memories, 5 retired agency pressures, and 11 retired interventions."} {"ts":"2026-05-22T19:24:10Z","type":"maintenance","status":"ok","note":"Completed swarm-wide state pocket-cleaning. Repaired all registered repo Face .cc files and Void self-state with pruned write semantics, removed old proof-loop receipts/state from Aqua/Epiphany/Libby/Bifrost and stale LocalCastBridge memories/cursor entries from Void, verified registered Face state files have 8 envelopes and zero retired entries; only Nibu retains witness wording as active Aetheria continuity/residue vocabulary."} +{"ts":"2026-05-22T19:45:13Z","type":"fix","status":"ok","note":"Pruned terminal audit residue from current self-state: write normalization now drops terminal open cases and keeps only queued/deferred candidate interventions. Repaired Void self-state from about 125KB to 95KB by removing 21 terminal candidates and 2 answered cases; speech receipts remain bounded audit evidence. Updated speech and memory lifecycle fixtures for the new contract."}