Skip to content

Windows full-test compatibility and POSIX capability markers (#173) - #184

Merged
hellices merged 17 commits into
mainfrom
feat/windows-test-compat
Aug 4, 2026
Merged

Windows full-test compatibility and POSIX capability markers (#173)#184
hellices merged 17 commits into
mainfrom
feat/windows-test-compat

Conversation

@hellices

@hellices hellices commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Closes #173

Result

A required Windows Python 3.12 job now runs the complete pytest suite on
every PR. Final verification on run 30936032385:

  • Windows: 3376 passed, 37 skipped, 0 failed
  • Linux 3.11 / 3.12 / 3.13: all SUCCESS
  • pre-commit, security, dependency review, CodeQL: all SUCCESS
  • fresh local full gate: 3392 passed, 21 skipped, ruff/mypy/tach green

The existing Linux matrix and coverage command are unchanged.

What changed

Explicit capability markers

  • New tests.platforms shared helpers (WINDOWS, POSIX,
    posix_only(reason)).
  • Only two newly classified tests are skipped: POSIX ~user account lookup.
  • Structural tests enforce capability-specific reasons and SHA-pinned actions.
  • Remote pod /tmp paths and mocked shell/approval/audit flows remain tested
    on Windows.

Security invariants remain cross-platform

  • Audit file fsync and fsync-failure fail-closed behavior run on Windows;
    POSIX additionally verifies directory/parent fsync.
  • Config, token store, log exports, and MCP endpoint files retain atomic
    writes and owner-only 0o600 creation requests.
  • POSIX asserts effective mode bits; Windows tests explicitly document that
    Python/NTFS mode emulation does not prove ACL confidentiality.
  • Windows config atomic replacement forces a restrictive 0o600 request
    instead of preserving meaningless emulated 0o666.

Product portability fixes

  • Exact LF UTF-8 log exports (newline="") instead of CRLF translation.
  • Local transfer paths use pathlib and native separators; remote paths stay
    POSIX.
  • Home-directory tests control both HOME and USERPROFILE.
  • Corporate-CA errors show readable Windows paths.
  • Shell/debug/node-shell/crash diagnostics use ASCII on non-UTF-8 consoles.
  • Late status refresh during teardown tolerates an unmounted StatusBar.

Write cancellation correctness

The Windows run exposed a real unawaited-coroutine race: mutation coroutines
were created before the audit-intent write and could be discarded unstarted
if cancellation landed during audit I/O.

  • _run_write, shielded agent writes, and proposal writes now receive an
    operation factory.
  • The mutation coroutine is created only after the intent record succeeds.
  • Cancellation before/during audit never creates a mutation coroutine.
  • Failed audit never invokes the mutation.
  • Tests use bounded condition waits; no scheduler sleeps.

Documentation

New docs/windows.md documents setup, CI guarantees, expected skips,
Developer Mode/symlink and terminal limitations, and the NTFS ACL boundary.

hellices and others added 2 commits August 4, 2026 22:03
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings August 4, 2026 13:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Introduces the first Windows CI compatibility slice and shared platform-test utilities.

Changes:

  • Adds a Windows Python 3.12 full-test job.
  • Adds POSIX/Windows capability helpers and action pin validation.
  • Adds tests and implementation notes for the new CI invariants.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
.github/workflows/ci.yml Adds the Windows test job.
tests/platforms.py Defines shared platform and workflow helpers.
tests/test_platforms.py Tests helpers and Windows CI structure.
.superpowers/sdd/task-1-report.md Records implementation and verification evidence.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_platforms.py Outdated
Comment thread .github/workflows/ci.yml
hellices and others added 11 commits August 4, 2026 22:43
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ask 3)

Preserve fail-closed, atomic-write, fsync, and chmod-request invariants on
Windows while acknowledging NTFS/Python POSIX-mode limitations:

- test_audit: Accept cross-platform OSError family for bad-path constructor
  laziness; split fsync count assertion (POSIX ≥2, Windows ≥1); mark
  directory-fsync-failure test posix_only with adjacent file-fsync test.
- test_config: Mode assertion uses spy on Windows to verify os.chmod(0o600)
  was requested; POSIX continues to assert stat result.
- test_logexport: Private-mode test verifies stat on POSIX, file existence
  on Windows (NTFS ignores POSIX bits in stat).
- test_server: Endpoint mode tests branch on os.name; Windows verifies
  atomic creation and valid content instead of stat mode bits.
- test_token_store: Mode test asserts stat on POSIX, file existence +
  valid JSON content on Windows.

All tests document the NTFS/Python POSIX-mode limitation explicitly in
docstrings. No production code changes needed — the code already passes
0o600 at creation which is the strongest portable guarantee.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… clarify docs

- Remove duplicate test_append_fails_closed_when_file_fsync_fails; add
  docstring to existing test_append_fails_closed_when_fsync_fails explaining
  it is the cross-platform (Windows-inclusive) counterpart to the adjacent
  posix_only directory-sync test.
- Replace inline os.name != 'nt' guards with shared POSIX constant from
  tests.platforms in test_config, test_logexport, test_server, test_token_store.
- Clarify MCP 'not merely chmodded' docstring: Windows branch explicitly
  states NTFS uses ACLs not mode bits and no ACL confidentiality is claimed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…view)

Critical product fix:
- _run_write / _run_write_inner / _run_write_shielded now accept
  Callable[[], Awaitable[None]] instead of Awaitable[None].
- Factory is invoked only after intent audit succeeds; a blocked or
  cancelled write never creates the mutation coroutine.
- Removed the fragile close() hack for leaked coroutines — the factory
  design eliminates the problem at the source.
- All call sites (confirm worker, operator install, subscription
  uninstall, agent write shield, proposal execution) converted to
  pass lambda/factory.

Related fixes:
- Replace Path.home staticmethod monkeypatches with portable HOME +
  USERPROFILE env setup; test_local_path_tilde_expanded also sets
  USERPROFILE for Windows.
- Path picker: rstrip(os.sep) before appending os.sep avoids // on root.
- __main__.py crash restart prompt: em dash replaced with ASCII --.

Tests:
- test_blocked_audit_never_invokes_op_factory: factory never called when
  intent audit fails (directory at log path).
- test_cancelled_before_factory_leaks_no_coroutine: threading-gated
  audit, cancel during to_thread, warnings-as-errors — factory never
  called, no unawaited coroutine.
- test_busy_bird: updated to pass factory not pre-created coroutine.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ease (#173 Task 4)

- test_write_ops: replace unbounded while/sleep loop with
  until(pilot, entered.is_set) for the gated-audit cancellation test.
- test_write_confirm_characterization: update group (a) prose to reflect
  factory-based _run_write (coroutine constructed post-audit, not inside
  confirmed branch).
- test_proposals_ui: remove inert rec.release.set() lines — factory
  design + _settle_interrupted_execution.write.cancel() properly unwinds
  the coroutine without needing the gate release.
- task-4-report: mark #6 proposals root cause as superseded by #7 factory.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ase (#173 Task 4)

Wrap the audit_gate.set() in a finally block so the executor thread
cannot hang at process exit if until() times out or the test fails
before reaching the release. Drop the catch_warnings/simplefilter
framing — the factory_calls invariant is what proves no coroutine
was created; warnings-as-errors was overclaiming.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…173 Task 4)

Product fix:
- on_external_proposals_changed: suppress NoMatches during teardown
  when StatusBar is already unmounted (same pattern used elsewhere in
  app.py). Normal errors still propagate.

Test fixes:
- test_c_cordons_node_after_approval: poll until success audit record
  is written (to_thread may still be in-flight after rec.calls lands
  on slower Windows).
- test_local_dir_pick_appends_remote_basename_verbatim: use
  str(tmp_path / 'report ') not hardcoded '/'; product Path join is
  correct and produces native separators.

Regression test:
- test_proposals_changed_during_teardown_does_not_raise: posts
  ExternalProposalsChanged after run_test exits (widget tree torn
  down); proves the guard suppresses NoMatches without swallowing
  normal errors.

Note: Linux 3.11 failure in the same CI workflow had no available log
blob — not addressed without evidence.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…er (#173 Task 4)

Replace vacuous post-after-exit test with one that runs inside
run_test: removes the StatusBar widget to simulate teardown state
while the message pump is active, then calls the handler directly.
A spy on _refresh_status proves the handler entered it (NoMatches
suppressed inside _refresh_status, not silently skipped).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…#173 Task 4)

Product fix:
- _refresh_status: catch NoMatches from missing StatusBar internally
  (teardown race). This is the single control point for 20+ callers
  including MCP _switch worker, proposals handler, navigation — none
  need individual guards. Removed the now-redundant handler-level
  suppress from on_external_proposals_changed.

Test fixes:
- test_review_approve_executes_with_the_bound_uid: poll store state
  == executed inside run_test with until() before exiting; the worker
  may still be marking the proposal as executed after rec.calls lands.
- test_refresh_status_tolerates_missing_status_bar: removes StatusBar
  while app is live, calls _refresh_status directly — proves the
  guard works for any caller path (MCP switch, navigation, etc.) and
  that normal refresh still works with the bar present.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ty (#173 Task 4)

Replace remaining em dash in the crash-cap stderr diagnostic
(line ~1135) with ASCII '--' so cp1252 terminals cannot raise
UnicodeEncodeError.

Regression: test_crash_cap_message_is_ascii_encodable exercises
the cap path and encodes the captured stderr as ASCII — any
non-ASCII character (em dash, arrow, etc.) would fail the encode.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@hellices
hellices marked this pull request as ready for review August 4, 2026 18:45
@hellices
hellices requested a balanced review from Copilot August 4, 2026 18:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated no new comments.

Suppressed comments (3)

docs/windows.md:30

  • This still leaves the issue's clean-Windows acceptance criterion unmet. The unchanged test_pack_dereferences_symlink_source calls Path.symlink_to() directly, so a normal Windows account without Developer Mode/elevation raises WinError 1314 instead of producing the documented pass/skip result. Add capability-aware handling for that test (for example, skip only when symlink creation is unavailable) and include it in the expected-skip contract.
- Symlink tests depend on Windows **Developer Mode** (or an elevated shell):
  native `Path.symlink_to()` can fail before korvid logic runs if symlink
  creation is not allowed.

tests/test_platforms.py:144

  • These values pin an older run even though the PR's final verification is run 30936032385 at the current head, whose Windows job reports 3376 passed and 37 skipped. As written, this invariant forces the contributor documentation to retain stale verification evidence; update both snippets together with docs/windows.md.
        "30930727214",
        "3373 passed / 37 skipped / 0 failures",

docs/windows.md:13

  • This cites an earlier commit as the proving run, while the PR description identifies final verification run 30936032385 and that run's Windows log reports 3376 passed / 37 skipped. Point the support claim at the successful current-head run and update the structural test that currently requires these old values.

This issue also appears on line 28 of the same file.

`windows-test` CI job green. The proving run for issue #173 was
`30930727214`: **3373 passed / 37 skipped / 0 failures**.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@hellices

hellices commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

First-review suppressed findings addressed in aa8c41d: capability-aware symlink handling skips only when Windows returns privilege-unavailable (WinError 1314/EPERM), docs and structural tests now cite final run 30936032385 (3376 passed / 37 skipped / 0 failures), and accidentally tracked SDD reports were removed. Independent local review approved.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated no new comments.

Suppressed comments (1)

docs/windows.md:24

  • This breakdown counts the newly added POSIX-only directory-fsync test as “pre-existing.” Before this PR there were three audit mode skips; tests/core/test_audit.py:255 adds the fourth audit skip, so the accurate split is 3 newly classified capability skips and 13 pre-existing ones (still 16 total).
  - **14 pre-existing platform skips** for POSIX-only permission semantics:
    7 local transfer permission-bit cases, 4 audit-log mode/directory-fsync
    cases, 2 transfer-stream late-permission-loss cases, and 1 unreadable CA
    bundle permission case.

hellices and others added 2 commits August 5, 2026 04:23
Transfer tests asserted audit entries after run_test exited, but the
threaded audit append (to_thread) may still be in-flight. Fix by
polling with until() for the expected outcome record before leaving
run_test:

- test_download_writes_file_and_audits: poll for 'success' audit
- test_upload_requires_approval_then_transfers: same
- test_progress_screen_escape_cancels_transfer: poll for 'cancelled'
- test_download_failure_notifies_and_audits_error: poll for 'error'

No sleeps; audit semantics unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The verified total remains 16 capability skips: 3 newly classified
(~user x2 plus directory fsync) and 13 pre-existing platform skips.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@hellices

hellices commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Second-review suppressed count finding fixed in f1e871a: the verified 16 capability skips are documented as 3 newly classified (~user x2 + directory fsync) and 13 pre-existing (transfer permission x7, audit mode x3, transfer-stream x2, CA permission x1). Structural docs test pins the corrected breakdown. The same pushed range includes 96ab6b6, which replaces an audit-outcome race with condition polling after a fresh full-gate reproduction. Full gate: 3397 passed, 21 skipped.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated no new comments.

Suppressed comments (1)

tests/core/test_config.py:604

  • The new suppression omits the required rationale (# type: ignore[code] # reason); the analogous spy at this file's line 666 includes one. Since this spy is only called with the Path passed by _atomic_write_text, narrowing its signature removes the suppression entirely.
            real_chmod(path, mode, *args, **kw)  # type: ignore[arg-type]

Remove the unnecessary type suppression and match the exact two-argument
call made by the atomic config writer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@hellices

hellices commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Review-loop limit reached: rounds 2 and 3 contained only suppressed advisory findings and no unresolved blocking threads. The final credible instruction issue was fixed by narrowing the chmod spy signature (no type suppression). Fresh full gate after the fix: 3397 passed, 21 skipped; ruff/mypy/tach green. Per AGENTS.md §8–9 no further Copilot review is requested; proceeding to required-check verification.

@hellices
hellices merged commit c11819f into main Aug 4, 2026
10 checks passed
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.

Windows dev environment: 56 tests fail on POSIX assumptions — add platform markers and Windows-equivalent invariant checks

2 participants