VoidBot/scripts/smoke-void-state-boundary-normalization-fixture.ps1
2026-06-08 19:20:11 +01:00

183 lines
7.1 KiB
PowerShell

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$repoRoot = Split-Path -Parent $PSScriptRoot
Set-Location -LiteralPath $repoRoot
$tempRoot = Join-Path $env:TEMP ("void-state-boundary-normalization-fixture-" + [guid]::NewGuid().ToString("n"))
$stateFilePath = Join-Path $tempRoot "void-self-state.cc"
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-OperationFile {
param(
[Parameter(Mandatory = $true)][hashtable] $Operation,
[Parameter(Mandatory = $true)][string] $Name
)
$path = Join-Path $tempRoot $Name
$Operation | ConvertTo-Json -Depth 32 | Set-Content -LiteralPath $path -Encoding UTF8
return $path
}
function Write-NodeScriptFile {
param(
[Parameter(Mandatory = $true)][string] $Script,
[Parameter(Mandatory = $true)][string] $Name
)
$path = Join-Path $tempRoot $Name
Set-Content -LiteralPath $path -Value $Script -Encoding UTF8
return $path
}
function Assert-NotFutureTimestamp {
param(
[Parameter(Mandatory = $true)][string] $Timestamp,
[Parameter(Mandatory = $true)][string] $Label
)
$parsed = [DateTimeOffset]::Parse($Timestamp)
$now = [DateTimeOffset]::UtcNow.AddSeconds(2)
if ($parsed -gt $now) {
throw "$Label is still in the future: $Timestamp"
}
}
try {
$futureMemory = @{
operation = "record_short_term_memory"
memory = @{
memoryId = "fixture-future-short-term"
kind = "project_seam"
target = @{ kind = "repo"; id = "VoidBot"; label = "VoidBot" }
summary = "Model-authored timestamps must not become canonical state time."
claim = "The parent state service owns canonical timestamps for model-originated writes."
tension = "If child timestamps are trusted, projection order can imply events that have not happened."
actionImplication = "Stamp short-term memory writes at the typed state boundary."
anchorRefs = @(
@{
ref = "fixture:future-memory"
kind = "fixture"
summary = "Future timestamp normalization fixture."
}
)
evidenceRefs = @()
createdAt = "2999-01-01T00:00:00.000Z"
updatedAt = "2999-01-01T00:00:00.000Z"
tags = @("repo:VoidBot", "topic:state-boundary")
}
}
$targetlessCandidate = @{
operation = "queue_candidate_intervention"
intervention = @{
interventionId = "fixture-targetless-queued"
kind = "self_advocacy"
status = "queued"
target = @{ kind = "system"; id = "typed-state-boundary"; label = "Typed state boundary" }
summary = "Queued candidate without a delivery target should not project as deliverable."
draft = "A queued candidate needs an actual delivery target, or it is theater with a clipboard."
priority = 0.5
mustEventuallyShare = $false
createdAt = "2999-01-01T00:00:00.000Z"
updatedAt = "2999-01-01T00:00:00.000Z"
tags = @("topic:state-boundary")
}
}
foreach ($entry in @(
@{ Operation = $futureMemory; Name = "future-memory.json" },
@{ Operation = $targetlessCandidate; Name = "targetless-candidate.json" }
)) {
$path = Write-OperationFile -Operation $entry.Operation -Name $entry.Name
Invoke-NodeChecked -Arguments @(".\scripts\void-self-state.mjs", "apply-operation", "--canonical", $stateFilePath, "--operation-file", $path) | Out-Null
}
$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); })", $stateFilePath)
$state = $stateJson | ConvertFrom-Json
$memory = @($state.thoughtMemory.shortTerm | Where-Object { $_.memoryId -eq "fixture-future-short-term" })[0]
$candidate = @($state.candidateInterventions.interventions | Where-Object { $_.interventionId -eq "fixture-targetless-queued" })[0]
$legacyFaceAffectScriptText = @'
const core = require(`${process.cwd()}/packages/core/dist/index.js`);
const legacy = {
schemaVersion: 1,
needs: [],
socialBonds: [],
userStatuses: [{
readId: "fixture-legacy-user-status",
target: { kind: "person", id: "metacrat", label: "Metacrat" },
status: "consulted",
summary: "Legacy userStatuses should decode as statusReads.",
claim: "The live authority is statusReads; userStatuses is only a migration alias.",
tension: "A stale persisted key should not keep the Face affect organ offline.",
actionImplication: "Decode legacy state and rewrite through the current strict schema.",
intensity: 0.6,
anchorRefs: [{ ref: "fixture:legacy-userStatuses", kind: "fixture" }],
evidenceRefs: [],
createdAt: "2026-06-08T00:00:00.000Z",
updatedAt: "2026-06-08T00:00:00.000Z",
tags: ["fixture:legacy"]
}],
moodDimensions: [],
socialBiases: [],
stressResponses: [{
name: "legacy-startle",
value: 0.4,
updatedAt: "2026-06-08T00:00:00.000Z"
}],
doctrineStances: [],
updatedAt: "2026-06-08T00:00:00.000Z"
};
console.log(JSON.stringify(core.voidFaceAffectSchema.parse(legacy)));
'@
$legacyFaceAffectScript = Write-NodeScriptFile -Name "legacy-face-affect.cjs" -Script $legacyFaceAffectScriptText
$legacyFaceAffectJson = Invoke-NodeChecked -Arguments @($legacyFaceAffectScript)
$legacyFaceAffect = $legacyFaceAffectJson | ConvertFrom-Json
if ($memory.createdAt -eq "2999-01-01T00:00:00.000Z" -or $memory.updatedAt -eq "2999-01-01T00:00:00.000Z") {
throw "Short-term memory kept model-authored future timestamps."
}
Assert-NotFutureTimestamp -Timestamp $memory.createdAt -Label "short-term createdAt"
Assert-NotFutureTimestamp -Timestamp $memory.updatedAt -Label "short-term updatedAt"
if ($candidate.status -ne "deferred") {
throw "Targetless queued candidate was not deferred; status is $($candidate.status)."
}
if (-not @($candidate.tags).Contains("normalized:targetless-queued-to-deferred")) {
throw "Targetless queued candidate was deferred without a normalization tag."
}
Assert-NotFutureTimestamp -Timestamp $candidate.createdAt -Label "candidate createdAt"
Assert-NotFutureTimestamp -Timestamp $candidate.updatedAt -Label "candidate updatedAt"
if (@($legacyFaceAffect.statusReads).Count -ne 1) {
throw "Legacy userStatuses did not decode into one statusReads entry."
}
if ($legacyFaceAffect.PSObject.Properties.Name -contains "userStatuses") {
throw "Legacy userStatuses survived parsed Face affect state."
}
if ($legacyFaceAffect.PSObject.Properties.Name -contains "stressResponses") {
throw "Legacy stressResponses survived parsed Face affect state."
}
@{
status = "ok"
shortTermMemoryId = $memory.memoryId
candidateStatus = $candidate.status
candidateTags = @($candidate.tags)
legacyFaceAffectStatusReads = @($legacyFaceAffect.statusReads).Count
} | ConvertTo-Json -Compress
} finally {
Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue
}