Repository navigation
Conversation
…Windows
Four `.mjs` entrypoints guarded `main()` with:
if (import.meta.url === `file://${process.argv[1]}`) { main(); }
On Windows `process.argv[1]` is `C:\...`, so the template produces
`file://C:\...` while `import.meta.url` is `file:///C:/...` (three slashes,
forward slashes). The comparison is never true, so the script exits 0 without
running `main()` and without writing its output -- a silent no-op that no
downstream check notices.
Fixed by comparing URL against URL, the idiom already used in
`integrations/isolation/acq-kits/paseo/files/home/paseo-register-mounts.mjs`:
process.argv[1] &&
import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href
Affected entrypoints:
- integrations/isolation/acq-kits/usai-provider/scripts/normalize-catalog.mjs
- integrations/isolation/acq-kits/opencode/files/home/opencode-config/render-provider-config.mjs
- integrations/isolation/acq-kits/paseo/files/home/paseo-assert-daemon-config.mjs
- integrations/providers/usai/scripts/build-catalog.mjs
New `scripts/tests/test_mjs_entrypoint_guards.py` runs each entrypoint as a
script through the interpreter (`node <file>`, not a shebang) and asserts it
did the work: the normalized catalog is written, the drift check reports on
the committed catalog, the OpenCode config is rendered, the daemon-config
assertion rejects an unsafe config. A static scan fails if the
`file://${process.argv[1]}` idiom reappears in any `.mjs`/`.js` file. The
tests run on every platform; on Windows they are the regression detectors.
AI-assisted (OpenCode [deepseek-v4.1-flash]); human review required.
…indows `integrations/isolation/schemas/fixtures/*/normalize.py` wrote their output with `Path.write_text(...)`, which applies the platform line separator. On Windows that produces CRLF, so the output diverged from the committed LF-only snapshot byte for byte (43 CR bytes for `usai`, 37 for `acme-ai`) even though the JSON was identical. Pass `newline="\n"` at the write site so the fixture emits LF on every platform. Only the source of the bytes changed: no byte-exact assertion was relaxed, and `integrations/isolation/schemas/fixtures/*/snapshot.json` is untouched. The `acme-ai` normalizer carries the identical defect and is fixed in the same commit. `test_fixture_normalizer_emits_lf_only_bytes` runs both normalizers with the current interpreter and asserts the output contains no CR bytes and is byte-identical to its snapshot. It fails on Windows before this change and passes after; on Linux and macOS it is a cheap invariant. AI-assisted (OpenCode [deepseek-v4.1-flash]); human review required.
Add a short "Working on Windows" note under the SBX section: - symlink creation needs Developer Mode (`SeCreateSymbolicLinkPrivilege`) or an elevated shell; - with `core.symlinks=false` the tracked `skills` symlink arrives as a plain 14-byte file holding its target (`.agents/skills`), so `skills/` does not materialise as a directory and a partial checkout on Windows is expected; - run Node scripts through the interpreter because Windows does not honour `#!/usr/bin/env node` shebangs; - `bash` on `PATH` is often WSL, not Git Bash. Factual notes only; no claim is made about local test-suite status. AI-assisted (OpenCode [deepseek-v4.1-flash]); human review required.
wz-gsa
left a comment
There was a problem hiding this comment.
Fixed-head adversarial review of 89ab3d5f57ba0cffc92def7ca8b9ca6e97e1c873 (full eight-file diff inspected; required checks pass). Major — integrations/isolation/acq-kits/paseo/files/home/paseo-assert-daemon-config.mjs:66-71: the new URL comparison still silently skips assertDaemonConfig when invoked through a symlink: Node resolves import.meta.url to the target while process.argv[1] names the link. I reproduced this on Node 22 in a network-disabled, read-only-source container: node /tmp/assert-daemon.mjs --paseo-home /tmp/no-daemon-config --listen 127.0.0.1:6767 exited 0 without checking the nonexistent configuration. The same entrypoint guard pattern is added to normalize-catalog.mjs, render-provider-config.mjs, and build-catalog.mjs. Canonicalize the invoked file path consistently (or fail closed when the invocation cannot be recognized), and add a symlink-invocation negative test for the daemon guard. Test quality — scripts/tests/test_mjs_entrypoint_guards.py:175-186: the renderer test permits any nonzero exit after writing a file as long as stderr is nonempty; require success on supported platforms, with a narrowly matched documented Windows post-rename EPERM case if needed. The Python LF fixture changes are sound on static inspection. No PR code executed on the host; the symlink probe ran in isolation. AI-assisted review (OpenCode). Rereview a corrected head.
Summary
Four
.mjsentrypoints never ranmain()on Windows: their guard comparedimport.meta.urlagainst afile://template ofprocess.argv[1], which does not produce the same string there. The scripts exited 0 and wrote nothing — a silent no-op. They now compare URL against URL.The two fixture normalizers in
integrations/isolation/schemas/fixtures/also wrote CRLF on Windows, so their output stopped matching the committed LF-only snapshots byte for byte. They now writenewline="\n".Eligibility & Accountability
@GSA-TTS/agentic-coding-teamcode-owner approval; eligibility is attested here, not authenticated). Anyone may open issues without this.AI Assistance Disclosure
deepseek-v4.1-flash) reproduced both defects on Windows, drafted the fix, and wrote the regression tests; a human reviewed the diff and the evidence below.Type of Change
Motivation
Defect 1 — the guard never fires on Windows
On Windows
process.argv[1]isC:\..., so the template yieldsfile://C:\..., whileimport.meta.urlisfile:///C:/...(three slashes, forward slashes). The two strings are never equal,main()never runs, and the process exits0having done nothing and written no output. A crash would be noticed; a silent exit 0 is not, which is why this needs a behavioural test rather than an exit-code check.Verified on
origin/main(7dd2ddc) on Windows 11 with Node v22.22.2:The portable idiom already exists in this repo (
integrations/isolation/acq-kits/paseo/files/home/paseo-register-mounts.mjs:262) and is now used by all four entrypoints:Fixed in:
integrations/isolation/acq-kits/usai-provider/scripts/normalize-catalog.mjsintegrations/isolation/acq-kits/opencode/files/home/opencode-config/render-provider-config.mjsintegrations/isolation/acq-kits/paseo/files/home/paseo-assert-daemon-config.mjsintegrations/providers/usai/scripts/build-catalog.mjsDefect 2 — fixture catalogs written with CRLF on Windows
integrations/isolation/schemas/fixtures/{usai,acme-ai}/normalize.pywrote their output withPath.write_text(...), which translates\nto the platform separator. On Windows that produced 43 CR bytes (usai) and 37 CR bytes (acme-ai) against snapshots that contain none, so the output diverged byte for byte even though the JSON was identical. Both now passnewline="\n"at the write site; no byte-exact assertion was relaxed and no snapshot was touched.Red/green evidence
Both states measured with the same command on the same host (Windows 11, Node v22.22.2, Python 3.12.10):
Red —
origin/mainplus only the new test file (source fixes stashed):Green — this branch:
Changes
fix(entrypoints)— portablemain()guards in the four.mjsentrypoints, each with a comment recording the failing strings, plus the new test file.fix(fixtures)—newline="\n"in both fixture normalizers, plus the LF/byte-exact regression test.docs(contributing)— a short "Working on Windows" note: symlinks need Developer Mode /SeCreateSymbolicLinkPrivilege; withcore.symlinks=falsethe trackedskillssymlink arrives as a plain 14-byte file (.agents/skills) soskills/does not materialise and a partial checkout is expected; run Node scripts through the interpreter because shebangs are not honoured;bashonPATHis often WSL, not Git Bash.New tests (
scripts/tests/test_mjs_entrypoint_guards.py, 11 cases) run on every platform — they are not Windows-skipped, because the entrypoint tests are meaningful invariants everywhere and are the actual regression detector on Windows. Each entrypoint is invoked as a script through the interpreter (node <file>, not a shebang) and must demonstrably do its work:normalize-catalog.mjswrites the neutral catalog (asserted on its contents);build-catalog.mjs --checkreports on the committed catalog offline (a non-zero exit is accepted only with a diagnostic — silence is the failure mode being tested);render-provider-config.mjsrendersopencode.jsoncfrom a fixture provider input root;paseo-assert-daemon-config.mjsrejects an unsafe config with exit 1 and a message, and accepts a safe one with exit 0;file://${process.argv[1]}(or its string-concat form) reappears in any.mjs/.cjs/.js/.mts/.ctsfile.scripts/tests/test_provider_input_contract.pyis deliberately untouched: another open PR owns that file.Testing
Actually run on Windows 11 (native, Python 3.12.10, Node v22.22.2):
pytest scripts/tests/test_mjs_entrypoint_guards.py -v— 11 passed (red before the fix: 11 failed).ruff check scripts— All checks passed.ruff format --check scripts— 42 files already formatted.python scripts/validate_repo.py— fails, for a pre-existing reason unrelated to this PR: theCross-reference & delegation-cycle validationvalidator reports 17unknown pattern '…'errors becauseskills/is checked out as a 14-byte file here (core.symlinks=false, no Developer Mode), so the skills patterns never materialise. It also needsPYTHONUTF8=1on this host to print✗. Both conditions exist onmainwithout this PR.python scripts/validate_provider_input_contract.pyis not run as evidence: it already fails on Windows onmainbecause of an exec-bit defect owned by fix(validator): make normalizer exec-bit check Windows-aware #547, which is out of scope here.What could not be verified locally
node: the entrypoint tests skip with an explicit reason instead of silently passing. CI has Node, so the class of regression is still covered there.render-provider-config.mjsexits1on Windows after successfully rendering, becausereplaceAtomicallycallsfsyncon a directory handle and Windows returnsEPERM; the renderedopencode.jsoncis already in place at that point. That is a separate defect from the guard, was not introduced here, and is not fixed here — the test asserts the render happened and that no failure is silent. Worth its own issue.Safety Checklist
prohibited_contentin output contract (n/a — no pattern content)Quality Checklist
experimentalfor new patterns (n/a — no new patterns)Validation Results
Related Issues
Relates to #547 — see the validation comment #547 (comment). That PR fixes a different Windows defect (exec bits in
scripts/validate_provider_input_contract.py); this PR does not touch that file or that validator.Additional Context
No
Co-authored-by:trailer is used; AI attribution follows the repository convention in commit bodies and the disclosure above.AI-assisted (OpenCode [deepseek-v4.1-flash]); human review required.