From a89ec4bfbe3ec95d0e2502a3da16eca5554fde12 Mon Sep 17 00:00:00 2001 From: jhuang-tw <77732008+jhuang-tw@users.noreply.github.com> Date: Tue, 11 Aug 2026 00:37:21 -0700 Subject: [PATCH] fix(evidence): preserve immutable Evidence records --- skills/scripts/append-evidence.ps1 | 26 +++++++++++++++++- skills/scripts/smoke.ps1 | 42 ++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/skills/scripts/append-evidence.ps1 b/skills/scripts/append-evidence.ps1 index e9b52cc..a2769ed 100644 --- a/skills/scripts/append-evidence.ps1 +++ b/skills/scripts/append-evidence.ps1 @@ -155,7 +155,31 @@ $notesBlock "@ $utf8 = New-Object System.Text.UTF8Encoding $false -[System.IO.File]::WriteAllText($path, $body, $utf8) +$stream = $null +$writer = $null +try { + try { + $stream = [System.IO.File]::Open( + $path, + [System.IO.FileMode]::CreateNew, + [System.IO.FileAccess]::Write, + [System.IO.FileShare]::None + ) + } catch [System.IO.IOException] { + if (Test-Path -LiteralPath $path) { + throw ("Evidence record already exists and is immutable: {0}. Use a new -Id." -f $fileName) + } + throw + } + $writer = [System.IO.StreamWriter]::new($stream, $utf8) + $writer.Write($body) +} finally { + if ($null -ne $writer) { + $writer.Dispose() + } elseif ($null -ne $stream) { + $stream.Dispose() + } +} $index = Join-Path $evDir 'INDEX.md' $line = "- $idSafe | $sev | $st | $titleLine | $fileName" diff --git a/skills/scripts/smoke.ps1 b/skills/scripts/smoke.ps1 index b057bc4..de25355 100644 --- a/skills/scripts/smoke.ps1 +++ b/skills/scripts/smoke.ps1 @@ -121,6 +121,48 @@ if (-not (Test-Path -LiteralPath $mr)) { } $routeSummary -join [Environment]::NewLine | Set-Content (Join-Path $LogDir '03-route-summary.txt') -Encoding UTF8 +# --- 4) Evidence ID immutability --- +$appendEvidence = Join-Path $scriptDir 'append-evidence.ps1' +$evidenceCase = Join-Path $LogDir 'evidence-immutability' +if (-not (Test-Path -LiteralPath $appendEvidence)) { + Bad 'append-evidence.ps1 missing for immutability check' +} else { + New-Item -ItemType Directory -Path $evidenceCase -Force | Out-Null + & powershell -NoProfile -ExecutionPolicy Bypass -File $appendEvidence ` + -CaseRoot $evidenceCase ` + -Id 'E-IMMUTABLE' ` + -Title 'first write' ` + -ReproCommand 'echo first' 2>&1 | Out-Null + $firstEvidenceExit = $LASTEXITCODE + if ($firstEvidenceExit -ne 0) { + Bad ("initial Evidence append exit {0}" -f $firstEvidenceExit) + } else { + $evidencePath = Join-Path $evidenceCase 'evidence\E-IMMUTABLE.md' + $indexPath = Join-Path $evidenceCase 'evidence\INDEX.md' + $beforeEvidence = (Get-FileHash -LiteralPath $evidencePath -Algorithm SHA256).Hash + $beforeIndex = (Get-FileHash -LiteralPath $indexPath -Algorithm SHA256).Hash + + & powershell -NoProfile -ExecutionPolicy Bypass -File $appendEvidence ` + -CaseRoot $evidenceCase ` + -Id 'E-IMMUTABLE' ` + -Title 'second write' ` + -ReproCommand 'echo second' 2>&1 | Out-Null + $duplicateEvidenceExit = $LASTEXITCODE + + $afterEvidence = (Get-FileHash -LiteralPath $evidencePath -Algorithm SHA256).Hash + $afterIndex = (Get-FileHash -LiteralPath $indexPath -Algorithm SHA256).Hash + if ($duplicateEvidenceExit -eq 0) { + Bad 'duplicate Evidence ID was accepted' + } elseif ($beforeEvidence -ne $afterEvidence) { + Bad 'existing Evidence changed after duplicate append' + } elseif ($beforeIndex -ne $afterIndex) { + Bad 'Evidence index changed after duplicate append' + } else { + Ok 'duplicate Evidence ID rejected without mutation' + } + } +} + # --- summary --- $summary = @( "VERIFY_EXIT=$verifyExit",