Skip to content

test(harness): restore VOYAGER_BIN / VOYAGER_PYTHON after bridge mechanics tests (fixes #6) - #7

Merged
HarryHeYu merged 1 commit into
HarryHeYu:mainfrom
SunnyRad:fix/issue6-restore-voyager-env
Nov 12, 2026
Merged

HarryHeYu merged 1 commit into
HarryHeYu:mainfrom
SunnyRad:fix/issue6-restore-voyager-env

Conversation

@SunnyRad

@SunnyRad SunnyRad commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

What was broken

Three mechanics tests in tests/bridge.test.ts (old lines 164 / 188 / 221) each set PATH='' and deleted VOYAGER_BIN and VOYAGER_PYTHON to prove discovery fails — but their finally restored only PATH. node:test runs a file's tests sequentially in one process, so the two deletions leaked into every later test. With VOYAGER_BIN configured as the only way to reach a real core (no shim on PATH), the two ambient-env integration tests that call integrationInfo() / runJson() with no BridgeOptions then failed with VOYAGER_NOT_FOUND. A normal PATH masks the leak, which is why CI stayed green. Tracked as #6, REPRODUCED_ON_MAIN.

How I reproduced it (real core, not a mock)

Isolated venv with Core 1.3.1 installed from source, a genuine voyager.exe, VOYAGER_BIN pointing at it, VOYAGER_PYTHON at the venv interpreter, PATH reduced to a non-existent directory (no fallback), VOYAGER_NO_SYNC=1. Whole-file run, exactly the command in the issue:

tree result
unmodified main 30 pass / 2 fail / 0 skipped — resolves the real core and reports its bridge schema and runJson returns parsed JSON from a real subcommand, both VOYAGER_NOT_FOUND
this PR 33 pass / 0 fail / 0 skipped — every integration test actually ran against the real core (not skipped into green)

What I changed — tests/bridge.test.ts only, plus a CHANGELOG entry

  1. withEnvRestored() / withoutVoyagerEnv() — saves all three mutated keys and restores each with restore-or-delete (the pattern recent-provider.test.ts already uses for SESSIONFLOW_ECHO_FILE; an absent key must be deleted, not set to the string "undefined"). The three mechanics tests now go through it. The async test body is awaited inside the try, so the environment is restored only after resolveVoyager() has settled.

    This detail mattered: my first version restored in the calling microtask, which put the ambient values back while the probe was still running. With a real core on PATH, two "no core" tests then passed by mistake — a test that cannot fail, the exact trap CONTRIBUTING.md rule 1 warns about. Fixed, and caught by the full run with a real core installed.

  2. A hermetic whole-file regression — a parent test re-runs this file with VOYAGER_BIN / VOYAGER_PYTHON sentinels and a non-existent PATH, and a child-half assertion checks all three keys survived the mechanics tests. It:

    • needs no installed core, so it cannot skip into a false green (the integration tests skip because no interpreter can seed an index);
    • strips NODE_TEST_CONTEXT, otherwise the nested node --test is treated as a recursive run and silently reports nothing (measured: empty stdout, node:test run() is being called recursively);
    • uses a non-empty non-existent PATH, because POSIX execvp falls back to a default search path when PATH is unset or empty — that would let a system python resolve and break hermeticity;
    • asserts the child really ran the mechanics tests and the env assertion, so a no-op child cannot look green.
  3. CHANGELOG.md [Unreleased] / Fixed entry.

Deliberately not touched: src/bridge.ts, resolveVoyager(), and the "a configured binary is authoritative, do not silently fall back" rule — the issue's scope note explicitly excludes them. No product code changed. Two files total: tests/bridge.test.ts, CHANGELOG.md.

Verification level

INTEGRATION_TESTED (real Core 1.3.1 called over the bridge) + UNIT_TESTED (the hermetic whole-file regression).

check result
npm ci / npm run build clean, Node v24.18.0 (engines: >=22.19)
npm test, no core installed 103 tests / 83 pass / 0 fail / 20 skipped
npm test, real core installed (mirrors CI plugin-tests on Linux) 103 / 103 pass / 0 fail / 0 skipped
maintainer scenario, unmodified main 30 pass / 2 fail (VOYAGER_NOT_FOUND)
maintainer scenario, this PR 33 pass / 0 fail
mutation: helper reverted to restore PATH only the new regression turns red with VOYAGER_BIN leaked out of a mechanics test (1 fail); restored → 0 fail

Notes

  • Branch is based on current upstream main (0f1724a), not my stale fork main. The commit has one parent and the diff is exactly the two files above.
  • github.com git transport is blocked on this host, so the branch was pushed through the GitHub Git Data API; content is identical to the locally tested files (SHA-256 verified).
  • The cold-start.yml workflow ran automatically for this fork PR (no approval gate was needed here, unlike Core PR #19). I will not push further commits after CI, so the result cannot be invalidated by a re-run.

fixes HarryHeYu#6)

Three mechanics tests in tests/bridge.test.ts emptied PATH and deleted
VOYAGER_BIN / VOYAGER_PYTHON to prove discovery fails, but their finally
restored only PATH. node:test runs a file's tests sequentially in one
process, so those deletions leaked into the later ambient-env integration
tests: with a configured core as the only way to reach a real one (no shim
on PATH), they failed with VOYAGER_NOT_FOUND. A normal PATH masked the leak.

- Add withEnvRestored()/withoutVoyagerEnv(): save all mutated keys and
  restore-or-delete each (the pattern recent-provider.test.ts already uses).
  Await the async test body inside the try so the env is restored only after
  resolveVoyager() has settled -- restoring in the calling microtask would
  put the ambient values back mid-probe and let a real core on PATH make a
  "no core" test pass by mistake.
- Add a hermetic whole-file regression: a parent test re-runs this file with
  VOYAGER_BIN / VOYAGER_PYTHON sentinels and a non-existent PATH (no core
  fallback); the child asserts all three keys survived the mechanics tests.
  NODE_TEST_CONTEXT is stripped so the nested node --test is not skipped as
  a recursive run. No installed core is required, so it cannot skip into a
  false green.
- CHANGELOG [Unreleased]/Fixed entry.

No product code changes: src/bridge.ts and the "configured binary is
authoritative" rule are untouched.
@SunnyRad

Copy link
Copy Markdown
Contributor Author

CI 结论补充:run 43073295149 已 completed,5/6 cold-start 矩阵 success,仅 plugin tests on linux 报 1 fail / 102 pass(103 tests)。

失败项是 tests/readme-consistency.test.ts:96 的 install examples pin a version, not a moving branch:

actual:   [ '1.3.1' ]
expected: [ '1.3.0' ]

这与本 PR 的改动无关,是当前 main 上既有的不一致:你最新的 0f1724a(docs: reflect Core v1.3.1 as released (README pins, pairing table, stale RC note))把两份 README 的 sessionFlow.git@v1.3.0 安装示例改成了 @v1.3.1,但测试第 104 行仍断言 ['1.3.0'],注释也还写着「v1.3.1 is a release candidate and must NOT appear as a stable pin yet」。

我在纯净上游 main 快照(不含本 PR 任何改动)上单独跑了 tests/readme-consistency.test.ts,同样 ✖ 报这组 actual 1.3.1 / expected 1.3.0——确认是 base 自带、任何基于当前 main 的 PR 都会继承。

本 PR 只改 tests/bridge.test.ts 与 CHANGELOG.md,未碰 README、未碰 readme-consistency.test.ts。按 issue #6 的「test-harness only / 不扩大范围」约束,我没有顺手修这条(而且 v1.3.1 发布文档正在你那边更新,测试期望该由你按发布口径定)。如果你希望我补一个把期望改成 ['1.3.1'] 并同步注释的小提交/另开 PR,告诉我即可。

本 PR 自身的验证仍然成立:本机装真实 Core 1.3.1 的全量 npm test 是 103/103 pass(我本地 README 是较早快照,故那条一致性测试通过);维护者场景 30/2 → 33/0;变异验证精确变红。

Verification level: INTEGRATION_TESTED + UNIT_TESTED。

@SunnyRad

Copy link
Copy Markdown
Contributor Author

补充一条可核验的证据,便于 Review 时省一步核查(不重复上一条的内容):

这 1 个红在 main 自己身上也独立复现了。 你 0f1724a 的 push run 43063357502(event=push,16:57:13Z)结论同样是 completed/failure:5 个 cold-start 矩阵全 success,只有 plugin tests on linux 挂在同一条 install examples pin a version, not a moving branch(pass 101 / fail 1;编号 94,本 PR 是 95 只因为多了 1 条测试)。上一个 commit e85f397 的 run 42993530120 还是 success,所以可以精确定位到 0f1724a 引入。

对照本 PR 的 run 43073295149:唯一的 not ok 就是同一条,其余 102 全绿;我新增的回归在 Ubuntu/Node 22.22.2 上明确是 ok 4 - a whole-file run with VOYAGER_BIN configured and no PATH fallback stays green (issue #6)。即本 PR 引入 0 个新失败,且核心修复在 Linux CI 上真实跑过(不是 skip)。

留给你的判断仍是那条口径问题:Core v1.3.1 已于 16:04Z 正式发布,README 安装示例改成 @v1.3.1 是对的,需要同步的是 tests/readme-consistency.test.ts:104 的 ['1.3.0'] 断言与其注释。按 issue #6 的范围约束我没有并进本 PR;你若希望我另开一个只改这一行的小 PR,说一声即可。

@SunnyRad

Copy link
Copy Markdown
Contributor Author

按我在上面两条评论里说的「需要就另开小 PR」,现在把它落地了:#8 test(readme): pin install examples to released Core v1.3.1(若编号有变以链接为准)。

它是独立分支、单 commit、单文件(tests/readme-consistency.test.ts,+4 −3),base 同为 0f1724a,与 PR #7 完全解耦:不碰 #7 的 tests/bridge.test.ts + CHANGELOG.md 两文件,不会让 #7 变成 3 文件、也不会作废 #7 已跑完的 CI(run 43073295149)。我这边不会往任一分支再推 commit。

之所以单独开:#7 的约束是 issue #6 的「test-harness only / 不扩大范围」,而这条是 0f1724a 自身漏改的测试期望值。把 ['1.3.0'] 同步成 ['1.3.1']、并清掉「v1.3.1 is a release candidate」这句过时注释,是 0f1724a 的标题(docs: reflect Core v1.3.1 as released ... stale RC note)与已发布的 v1.3.1 tag 的直接结论,不是新的发布口径判断——具体注释措辞仍由你定,我不替你裁定。

验证(纯净 main 快照 + 本机):改前 6/5/1(actual ['1.3.1'] / expected ['1.3.0'])、改后 6/6/0、变异回 ['1.3.0'] 精确变红、复原 byte-identical、npm ci+build 干净、全量 npm test = 102 / 82 pass / 0 fail / 20 skipped。

两条 PR 现在各自独立可审:你可以先合 #8 让 main 恢复绿,再回头 Review #7(届时 #7 只需要一次 re-run 就能全绿);也可以关掉 #8 自己改。我不会合并任何东西。

顺带说明一处更新:#7 正文里「不推进一步 commit 以免作废已跑完的 CI」这句仍然成立——我这里推的是另一个分支,不是 #7 的 fix/issue6-restore-voyager-env。

@HarryHeYu HarryHeYu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE — reviewed against a real Core 1.3.1, not just the diff

Thank you @SunnyRad for the careful diagnosis and for keeping the change to two files. I verified each of the three things that matter here independently, against the real Core 1.3.1 (voyager.cli, schema_version: 1, all ops present), not a mock:

  1. Env restoration is correct. withEnvRestored snapshots all three mutated keys up front and uses restore-or-delete (value === undefined -> delete), matching the recent-provider.test.ts convention. withoutVoyagerEnv covers PATH, VOYAGER_BIN, VOYAGER_PYTHON — the exact keys the three mechanics tests delete.
  2. Async work completes before finally restores. return await fn() is inside the try, so restore() runs only after resolveVoyager() has settled. I specifically checked the trap described in the PR body (restoring in the calling microtask while the probe is still in flight); the shipped shape avoids it. Also confirmed resetBridgeCache() is in the finally, so a mutated env cannot leave a stale cached resolution behind.
  3. The whole-file regression genuinely detects the old defect. I ran a file-level mutation — reverting the helper to save only PATH (the pre-fix behaviour) — and the regression turned red with exactly VOYAGER_BIN leaked out of a mechanics test. The child-half assertion reads values captured at module load, strips NODE_TEST_CONTEXT, uses a non-empty non-existent PATH, and asserts the child really ran both the mechanics tests and the env assertion, so it cannot pass as a silent no-op or skip into green.

Joint verification with real Core 1.3.1 (isolated home, VOYAGER_NO_SYNC=1, VOYAGER_PYTHON pinned, PYTHONPATH to the Core source):

# tests 33   # pass 33   # fail 0   # skipped 0
ok  - a whole-file run with VOYAGER_BIN configured and no PATH fallback stays green (issue #6)
ok  - resolves the real core and reports its bridge schema
ok  - runJson returns parsed JSON from a real subcommand
ok  - search finds a seeded session and reports its excerpt

Zero skipped: the integration tests actually executed against the real core.

CI: the one red plugin tests on linux is not introduced by this PR. It is the base-carried readme-consistency.test.ts:104 assertion (expected '1.3.0' / actual '1.3.1') inherited from 0f1724a; it reproduces on pristine main (run 43063357502). The other 5 cold-start matrix jobs are green. That assertion has now been corrected on main in c4c658a (CI 43156366514 = success), so this PR introduces 0 new failures and the merged main will carry both fixes.

No product code changed; resolveVoyager() and the "configured binary is authoritative" rule are untouched, as the issue scope requires. Squash-merging with you retained as author.

@HarryHeYu
HarryHeYu merged commit f39be17 into HarryHeYu:main Nov 12, 2026
5 of 6 checks passed
@HarryHeYu

Copy link
Copy Markdown
Owner

Merged as f39be17 (squash), with @SunnyRad retained as commit author. Thank you — this was an unusually well-scoped contribution: a real reproduction on unmodified main, a fix that touched only the test harness, a regression that cannot skip into a false green, and a mutation check proving the regression actually fails on the old behaviour.

I independently re-verified against real Core 1.3.1: 33 pass / 0 fail / 0 skipped, with resolves the real core, runJson, and search all genuinely executing, and the whole-file regression turning precisely red (VOYAGER_BIN leaked out of a mechanics test) when the helper is reverted to restoring only PATH.

The separate readme-consistency pin (['1.3.0'] → ['1.3.1']) was fixed on main in c4c658a (CI success), so merged main now carries both fixes. Closing #6 as resolved. Also closing #8, which is the same one-line readme pin already covered by c4c658a — thank you for splitting it out and for explicitly leaving the wording decision to the maintainer; no contribution is lost, the pin is in main and credited in the review above.

HarryHeYu added a commit that referenced this pull request Nov 12, 2026
…ore #22

Correct stale states: Core v1.3.1 is RELEASED (tag 81d7e3b), issues #1 and #6 are closed (PR #7 f39be17). Link the evidence_verify item to the decided design in dsh#9 and the new Core issue #22. OpenCode / provenance / native resume / VSIX / macOS CI keep their existing not-verified wording; nothing is promoted to done.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants