From 3a0be5db9176f65539e351f67e97dda45a160217 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 09:41:26 +0000 Subject: [PATCH] test(windows): guard harness-learning symlink fixtures behind the symlink-privilege probe P520 node_verify:test on main 3d8ff326 (job 4507c7e2) failed in harness-learning: three tests used raw symlinkSync, which needs privilege on non-admin Windows. They now probe once and skip with a NOT EXECUTED diagnostic; GitHub Actions still requires the privilege and executes them. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01AjjQnxAvtbQCFYE3y8yrq8 --- tests/harness-learning.test.js | 4 ++++ tests/windows-symlink-capability.js | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/tests/harness-learning.test.js b/tests/harness-learning.test.js index 8ca8d68ef..4942f6302 100644 --- a/tests/harness-learning.test.js +++ b/tests/harness-learning.test.js @@ -3,6 +3,7 @@ import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { symlinkFixtureAvailable } from "./windows-symlink-capability.js"; import { HarnessLearningStore, reviewHarnessEvidence, @@ -229,6 +230,7 @@ test("dangling journal symlink is corrupt, not an empty journal", (t) => { const s = store(t); const project = "kmj-codebridge"; const file = s.filename(project); + if (!symlinkFixtureAvailable(t, path.dirname(file))) return; const missingTarget = path.join(path.dirname(file), "missing-journal.json"); fs.symlinkSync(missingTarget, file); assert.equal(fs.existsSync(file), false); @@ -252,6 +254,7 @@ test("dangling journal symlink is corrupt, not an empty journal", (t) => { test("symlinked or corrupt journal fails closed", (t) => { const s = store(t); const p = "kmj-codebridge"; + if (!symlinkFixtureAvailable(t, path.dirname(s.filename(p)))) return; s.record(p, { ...evidence, requestKey: "first", @@ -274,6 +277,7 @@ test("symlinked or corrupt journal fails closed", (t) => { test("journal path swap between lstat and open fails closed", (t) => { const s = store(t); const project = "kmj-codebridge"; + if (!symlinkFixtureAvailable(t, path.dirname(s.filename(project)))) return; s.record(project, { ...evidence, requestKey: "initial", diff --git a/tests/windows-symlink-capability.js b/tests/windows-symlink-capability.js index 0b2d3a27c..c075cb420 100644 --- a/tests/windows-symlink-capability.js +++ b/tests/windows-symlink-capability.js @@ -1,4 +1,5 @@ import fs from "node:fs"; +import path from "node:path"; /** * Only symbolic-link fixture creation is optional on non-admin Windows @@ -29,3 +30,26 @@ export function tryFileSymlink(t, target, link) { return false; } } + +/** + * For tests that create the link inside a hook or helper: probe once up front. + * Returns false (after t.skip) only on a non-audited Windows workstation. + */ +export function symlinkFixtureAvailable(t, dir) { + const target = path.join(dir, "symlink-probe-target"); + const link = path.join(dir, "symlink-probe-link"); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(target, "probe"); + try { + if (!tryFileSymlink(t, target, link)) { + t.skip( + "Windows cannot create symlink fixture; security assertion NOT EXECUTED", + ); + return false; + } + return true; + } finally { + fs.rmSync(link, { force: true }); + fs.rmSync(target, { force: true }); + } +}