Repository navigation
Conversation
Windows' CreateProcess only loads PE images, so executing a shebang script directly fails with WinError 193. That made test_usai_normalizer_output_satisfies_runtime_contract and test_fixture_normalizers_are_directly_invocable POSIX-only despite asserting platform-independent behaviour. Launch normalizers the way the runtime does instead: .mjs through node and .py through sys.executable, via one shared helper. A missing node is a skip with an explicit reason rather than a silent pass. The contract assertions are unchanged.
The base branch now provides _symlink_skip_reason and requires_symlink_support and already decorates test_windows_contract_keeps_symlink_and_inside_root_checks, so the copy of that helper on this branch is redundant. Drop the duplicate helper and apply the base decorator to the two symlink tests the base does not cover: test_runtime_validator_rejects_artifact_symlink and test_runtime_validator_rejects_normalizer_symlink. Test bodies and assertions are untouched.
mogul
force-pushed
the
test/windows-harness-portability
branch
from
October 30, 2026 21:23
ecba5a5 to
3ac3458
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes
scripts/tests/test_provider_input_contract.pyportable to Windows by launching normalizers through the interpreter the runtime uses, instead of executing shebang scripts directly. Windows'CreateProcessonly loads PE images, sosubprocess.run([str(script_path), ...])fails withWinError 193there and only ever worked on POSIX.This PR is stacked on #547 and targets
fix/validator-windows-exec-bit(a07c3d5). When #547 merges intomain, this PR retargets tomainautomatically.The symlink gating in this file now comes from the base branch, not from this PR. #547 at
a07c3d5already defines_symlink_skip_reason()andrequires_symlink_supportand already decoratestest_windows_contract_keeps_symlink_and_inside_root_checks. This PR reuses that decorator and does not define a second copy; it only applies it to the two symlink tests the base does not yet cover.Tests only. The diff touches exactly one file,
scripts/tests/test_provider_input_contract.py(+26/−2). No product code changed, no assertion relaxed.Evidence chain
Measured on a real Windows 11 host (native python.org 3.12.10,
os.name == "nt"), running just this test file:origin/main7dd2ddc9af3707a07c3d5(current base)3ac3458)At
main, 15 of those 19 failures are the validator exec-bit defect that #547 fixes (st_mode & 0o111is always0on Windows, so every bundle is rejected withnormalizerPath must be an executable file). At9af3707the remaining harness failures were 2 ×WinError 193and 3 ×WinError 1314. Ata07c3d5, the base's own gate already removes oneWinError 1314failure; the 4 left are 2 ×WinError 193and 2 ×WinError 1314— both classes are removed on this branch.Originating validation: #547 (comment)
What changed (this PR's contribution)
_normalizer_argv()— one shared helper that builds argv the way the runtime launches a normalizer:.mjsthroughnode,.pythroughsys.executable. A missingnodeis a skip with an explicit reason, never a silent pass.test_usai_normalizer_output_satisfies_runtime_contractandtest_fixture_normalizers_are_directly_invocable. Their contract assertions are unchanged.@requires_symlink_supporton the two symlink tests the base does not decorate —test_runtime_validator_rejects_artifact_symlinkandtest_runtime_validator_rejects_normalizer_symlink. The decorator itself is the base's.Explicitly not included: any second copy of
_symlink_skip_reason()/requires_symlink_support, and any decoration oftest_windows_contract_keeps_symlink_and_inside_root_checks(the base already does it, a second decorator would be redundant).The 2 remaining failures
Both are product/fixture defects, and both now surface as real assertion/contract failures rather than
WinError 193— the normalizers actually execute, which is the point of this PR.WinError 193appears zero times in the run.test_usai_normalizer_output_satisfies_runtime_contractContractError: … catalog.json: cannot read JSON: [Errno 2] No such file or directorynormalize-catalog.mjsguards onimport.meta.url === `file://${process.argv[1]}`, which isfile:///C:/…vsfile://C:\…on Windows, somain()never runs: exit 0, no output written. Silent no-op.test_fixture_normalizers_are_directly_invocableAssertionError: b'{\r\n …' != b'{\n …'(at index 1:b'\r' != b'\n')normalize.pywrites withPath.write_text(...), which translates\nto CRLF on Windows while the committed snapshot is LF. JSON is equal; only bytes differ.#556 (
fix/windows-mjs-entrypoints) touches bothnormalize-catalog.mjsand the two fixturenormalize.pyfiles (addingnewline="\n"), so it is the finish line for these two.Skip list
test_runtime_validator_rejects_artifact_symlinkSeCreateSymbolicLinkPrivilegenot held / no symlink-capable filesystemtest_runtime_validator_rejects_normalizer_symlinktest_windows_contract_keeps_symlink_and_inside_root_checkstest_runtime_validator_rejects_non_executable_normalizer_on_posixPOSIX mode bits are not reported on Windowstest_runtime_validator_ignores_shebang_when_posix_mode_bits_missingPOSIX mode bits are not reported on WindowsGitHub's Windows runners have Developer Mode enabled, so the three symlink tests execute there. On this reporting host they skip because the account cannot create symlinks at all (
WinError 1314).Are the security assertions weakened?
No. All three symlink test bodies are untouched; the only change is a decorator, and the gate is capability-based (a real symlink attempt in a temporary directory), not
os.name-based. Because this host cannot create symlinks at all, the gate's behaviour was verified by simulating the probe: a capable host returns the test unchanged (no skip); an incapable host attaches the skip with its reason; and if the probe wrongly claimed capability the test fails loudly withOSError winerror=1314rather than passing. The assertions were not exercised on a symlink-capable host locally — CI is authoritative there.Validation
makeis not onPATHon this host, somake validatecould not be run as written. Its equivalent,python scripts/validate_repo.py, fails here for a reason unrelated to this change:skillsis a tracked symlink (mode120000→.agents/skills) that git materialises as a plain text file when the account cannot create symlinks, so the pattern tree is unreachable and 17 reference errors are reported. That is a property of an unprivileged Windows checkout, not a regression from this PR.ci.yml(andfrontend-skills-qa.yml,zizmor.yml) trigger only onpull_requesttomain, so a PR based onfix/validator-windows-exec-bitgets onlypr-lint. Linux behaviour is unchanged by this PR, and this branch's CI should be green once fix(validator): make normalizer exec-bit check Windows-aware #547 merges and this PR retargets tomain; on POSIX the symlink tests run as before and thesys.executable/nodeprefix is a normal launch. I am not claiming local Linux results.What this does not change
scripts/validate_provider_input_contract.pyis untouched (that is fix(validator): make normalizer exec-bit check Windows-aware #547's subject).os.name == "nt"skip; the only skip condition involved is the base's probed symlink capability.AI Assistance Disclosure
AI-assisted (OpenCode [deepseek-v4.1-flash]); human review required.
Eligibility & Accountability
Left unchecked deliberately: eligibility and authorship attestation are for a human to make, not for the AI assistant that drafted this PR.
Type of Change
Testing
make validatelocally —makeis absent on this Windows host; see Validation above for the equivalent command and its environment-specific resultpytest scripts/tests/test_provider_input_contract.py) on native Windowsos.name == "nt"Safety Checklist
Validation Results
Related Issues
Relates to #547 (stacked base)
Relates to #556 (owner of the 2 remaining failures)
Additional Context
Sequence for "green on Windows": land #547, land this branch, land #556.
Worth noting for the wider effort: a default non-elevated Windows host cannot materialise this repository's
skillsgit symlink at all, somake validateand part of the broader suite cannot run there regardless of this PR. That is a separate decision about whether Windows support requires Developer Mode.Reviewer Notes
Reviewers should verify:
scripts/tests/test_provider_input_contract.pyand contains no product-code change_symlink_skip_reason()/requires_symlink_supportis introduced — the base's is reusedtest_windows_contract_keeps_symlink_and_inside_root_checksis decorated exactly once (by the base)WinError 193