feat(system): DPLAN-0172 windows_compat — standard + 39 file fixes across 9 branches

Co-Authored-By: @devpulse <devpulse@aipass>
This commit is contained in:
AIOSAI
2026-05-10 20:55:16 -07:00
co-authored by @devpulse
parent aee28993f5
commit 8a634dd0f3
3 changed files with 569 additions and 0 deletions
@@ -0,0 +1,144 @@
# Windows Compatibility
**Status:** Draft v1
**Date:** 2026-05-10
---
## What It Is
A standard that ensures Python code runs on both Linux and Windows by detecting
POSIX-only APIs used without platform guards. Windows CI (GitHub Actions on
Windows Server 2022) validates compliance end-to-end.
---
## Why It Matters
AIPass is pip-installable and must work cross-platform. POSIX-only patterns
like bare `import fcntl` or `os.waitpid(-1, os.WNOHANG)` crash immediately on
Windows with `ImportError` or `WinError 87`. The Windows CI caught 71 failures
from these systemic patterns. This standard prevents regressions.
---
## What the Checker Scans For
AST-parses every `.py` file and flags POSIX-only usage outside platform guards.
### Rule 1 — POSIX-only imports
`import fcntl`, `import pwd`, `import grp`, `import termios`, `import resource`
without `if sys.platform` or `try/except ImportError`.
### Rule 2 — POSIX-only constants
`os.WNOHANG`, `signal.SIGPIPE` without platform guard or `hasattr()` check.
### Rule 3 — POSIX-only calls
`os.fork()`, `os.setpgid()`, `os.killpg()`, `os.getpgid()`, `os.waitpid()`
without platform guard.
### Rule 4 — os.kill without exception handling
`os.kill(pid, signal)` without `try/except OSError`. Windows raises
`OSError: [WinError 87] The parameter is incorrect` for invalid PIDs.
### Valid Guards (recognized by checker)
- `if sys.platform != "win32":` / `if sys.platform == "linux":`
- `if os.name != "nt":` / `if os.name == "posix":`
- `try: ... except ImportError:`
- `try: ... except OSError:`
- `if hasattr(signal, "SIGPIPE"):`
### Skips
- `__init__.py` files
- Non-`.py` files
---
## Code Examples
### Violation — bare POSIX import
```python
import fcntl
fcntl.flock(fd, fcntl.LOCK_EX)
```
### Fix 1 — platform guard with Windows fallback
```python
if sys.platform == "win32":
import msvcrt
else:
import fcntl
def lock_file(fd):
if sys.platform == "win32":
msvcrt.locking(fd, msvcrt.LK_LOCK, 1)
else:
fcntl.flock(fd, fcntl.LOCK_EX)
```
### Fix 2 — try/except import
```python
try:
import fcntl
except ImportError:
fcntl = None
```
### Violation — unguarded os.WNOHANG
```python
pid, _ = os.waitpid(-1, os.WNOHANG)
```
### Fix — platform guard
```python
if sys.platform != "win32":
pid, _ = os.waitpid(-1, os.WNOHANG)
```
### Violation — os.kill without exception handling
```python
os.kill(pid, 0) # Crashes on Windows with WinError 87
```
### Fix — wrap in try/except
```python
try:
os.kill(pid, 0)
except OSError:
return False
```
---
## Scoring
- **Scope:** AUDIT_SCOPE = "all_files"
- **Checks per file:** 1 (Windows compat)
- **Score 100:** No unguarded POSIX-only patterns found
- **Score 0:** One or more unguarded patterns found
- **Failure message:** "N unguarded POSIX pattern(s): L42: import fcntl; ..."
- **Overall pass threshold:** 75%
---
## Bypass
File-level bypass (entire file is Linux-only):
```json
{"file": "apps/handlers/dispatch/daemon.py", "standard": "windows_compat",
"reason": "Daemon process management is Linux-only by design"}
```
Line-level bypass (specific line has a valid reason):
```json
{"file": "apps/config.py", "standard": "windows_compat", "lines": [89],
"reason": "fcntl used only in POSIX lock path, Windows path above"}
```
---
## Reference
- **Checker:** windows_compat_check.py
- **Scope:** all_files
- **Entry point:** check_module()
- **Standard label:** WINDOWS_COMPAT
- **Windows CI:** .github/workflows/windows-test.yml
- **Issue:** #326 (Input-X Windows platform report)
@@ -0,0 +1,305 @@
# =================== AIPass ====================
# Name: windows_compat_check.py
# Description: Windows Compatibility Standards Checker Handler
# Version: 1.0.0
# Created: 2026-05-10
# Modified: 2026-05-10
# =============================================
"""
Windows Compatibility Standards Checker Handler
Detects POSIX-only patterns that will crash or behave incorrectly on
Windows. Scans for:
1. Unguarded POSIX-only imports (fcntl, pwd, grp, termios, resource)
2. Unguarded POSIX-only constants (os.WNOHANG, signal.SIGPIPE)
3. Unguarded POSIX-only calls (os.fork, os.setpgid, os.killpg, os.waitpid)
4. os.kill() without try/except catching OSError
Detection uses AST parsing to identify violations and check whether they
sit inside a platform guard (if sys.platform / if os.name / try-except
ImportError).
"""
import ast
from pathlib import Path
from typing import Dict
from aipass.prax import logger
from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed
from aipass.seedgo.apps.handlers.json import json_handler
AUDIT_SCOPE = "all_files"
_POSIX_ONLY_MODULES = frozenset({"fcntl", "pwd", "grp", "termios", "resource"})
_POSIX_ONLY_OS_ATTRS = frozenset({"WNOHANG"})
_POSIX_ONLY_OS_CALLS = frozenset({"fork", "setpgid", "killpg", "getpgid", "waitpid"})
_GUARDED_EXCEPT_TYPES = frozenset(
{
"ImportError",
"ModuleNotFoundError",
"OSError",
"PermissionError",
"ProcessLookupError",
}
)
def _is_platform_guard(node: ast.expr) -> bool:
"""Return True if the test expression is a sys.platform or os.name comparison."""
if isinstance(node, ast.Compare):
left = node.left
if isinstance(left, ast.Attribute) and isinstance(left.value, ast.Name):
if left.value.id == "sys" and left.attr == "platform":
return True
if left.value.id == "os" and left.attr == "name":
return True
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
if node.func.id == "hasattr":
return True
if isinstance(node, ast.BoolOp):
return any(_is_platform_guard(v) for v in node.values)
return False
def _collect_child_linenos(node: ast.AST) -> set[int]:
"""Walk all descendants and return line numbers where present."""
lines: set[int] = set()
for child in ast.walk(node):
lineno = getattr(child, "lineno", None)
if lineno is not None:
lines.add(lineno)
return lines
def _handler_catches_guarded_type(handler: ast.ExceptHandler) -> bool:
"""Return True if the except handler catches an import/OS error type."""
if handler.type is None:
return True
if isinstance(handler.type, ast.Name):
return handler.type.id in _GUARDED_EXCEPT_TYPES
if isinstance(handler.type, ast.Tuple):
return any(isinstance(elt, ast.Name) and elt.id in _GUARDED_EXCEPT_TYPES for elt in handler.type.elts)
return False
def _lines_in_guarded_blocks(tree: ast.Module) -> set[int]:
"""Collect line numbers inside platform guard blocks or try/except ImportError."""
guarded: set[int] = set()
for node in ast.walk(tree):
if isinstance(node, ast.If) and _is_platform_guard(node.test):
guarded.update(_collect_child_linenos(node))
if isinstance(node, ast.Try):
if any(_handler_catches_guarded_type(h) for h in node.handlers):
guarded.update(_collect_child_linenos(node))
return guarded
def _check_import_node(node: ast.Import, guarded: set[int]) -> list[tuple[int, str]]:
"""Check a single Import node for POSIX-only module names."""
return [
(node.lineno, f"import {alias.name}")
for alias in node.names
if alias.name in _POSIX_ONLY_MODULES and node.lineno not in guarded
]
def _find_posix_import_violations(tree: ast.Module, guarded: set[int]) -> list[tuple[int, str]]:
"""Find unguarded imports of POSIX-only modules."""
violations: list[tuple[int, str]] = []
for node in ast.walk(tree):
if isinstance(node, ast.Import):
violations.extend(_check_import_node(node, guarded))
elif isinstance(node, ast.ImportFrom):
if not node.module:
continue
if node.module.split(".")[0] in _POSIX_ONLY_MODULES and node.lineno not in guarded:
violations.append((node.lineno, f"from {node.module} import ..."))
return violations
def _find_posix_constant_violations(tree: ast.Module, guarded: set[int]) -> list[tuple[int, str]]:
"""Find unguarded references to POSIX-only constants like os.WNOHANG."""
violations: list[tuple[int, str]] = []
for node in ast.walk(tree):
if not isinstance(node, ast.Attribute) or not isinstance(node.value, ast.Name):
continue
if node.value.id == "os" and node.attr in _POSIX_ONLY_OS_ATTRS:
if node.lineno not in guarded:
violations.append((node.lineno, f"os.{node.attr}"))
if node.value.id == "signal" and node.attr == "SIGPIPE":
if node.lineno not in guarded:
violations.append((node.lineno, "signal.SIGPIPE"))
return violations
def _find_posix_call_violations(tree: ast.Module, guarded: set[int]) -> list[tuple[int, str]]:
"""Find unguarded calls to POSIX-only os functions."""
violations: list[tuple[int, str]] = []
for node in ast.walk(tree):
if not isinstance(node, ast.Call):
continue
func = node.func
if not isinstance(func, ast.Attribute) or not isinstance(func.value, ast.Name):
continue
if func.value.id == "os" and func.attr in _POSIX_ONLY_OS_CALLS:
if node.lineno not in guarded:
violations.append((node.lineno, f"os.{func.attr}()"))
return violations
def _find_os_kill_violations(tree: ast.Module, guarded: set[int]) -> list[tuple[int, str]]:
"""Find os.kill() calls not inside a try/except that catches OSError."""
violations: list[tuple[int, str]] = []
for node in ast.walk(tree):
if not isinstance(node, ast.Call):
continue
func = node.func
if (
isinstance(func, ast.Attribute)
and isinstance(func.value, ast.Name)
and func.value.id == "os"
and func.attr == "kill"
and node.lineno not in guarded
):
violations.append((node.lineno, "os.kill() without OSError handling"))
return violations
def check_module(module_path: str, bypass_rules: list | None = None) -> Dict:
"""Check a Python file for Windows-incompatible patterns."""
path = Path(module_path)
if is_bypassed(module_path, "windows_compat", bypass_rules=bypass_rules):
return {
"passed": True,
"checks": [
{
"name": "Bypassed",
"passed": True,
"message": "Standard bypassed via .seedgo/bypass.json",
}
],
"score": 100,
"standard": "WINDOWS_COMPAT",
}
if path.suffix != ".py" or path.name == "__init__.py":
return {
"passed": True,
"checks": [
{
"name": "Windows compat",
"passed": True,
"message": "File skipped (non-target)",
}
],
"score": 100,
"standard": "WINDOWS_COMPAT",
}
if not path.exists():
return {
"passed": False,
"checks": [
{
"name": "File exists",
"passed": False,
"message": f"File not found: {module_path}",
}
],
"score": 0,
"standard": "WINDOWS_COMPAT",
}
try:
source = path.read_text(encoding="utf-8")
except Exception as e:
logger.info("Cannot read %s: %s", path, e)
return {
"passed": False,
"checks": [
{
"name": "File readable",
"passed": False,
"message": f"Error reading file: {e}",
}
],
"score": 0,
"standard": "WINDOWS_COMPAT",
}
try:
tree = ast.parse(source, filename=str(path))
except SyntaxError as e:
logger.info("Skipped %s: SyntaxError during parse", path)
return {
"passed": False,
"checks": [
{
"name": "File parseable",
"passed": False,
"message": f"Syntax error: {e}",
}
],
"score": 0,
"standard": "WINDOWS_COMPAT",
}
guarded = _lines_in_guarded_blocks(tree)
all_violations: list[tuple[int, str]] = []
all_violations.extend(_find_posix_import_violations(tree, guarded))
all_violations.extend(_find_posix_constant_violations(tree, guarded))
all_violations.extend(_find_posix_call_violations(tree, guarded))
all_violations.extend(_find_os_kill_violations(tree, guarded))
non_bypassed = [
(ln, desc) for ln, desc in all_violations if not is_bypassed(module_path, "windows_compat", ln, bypass_rules)
]
non_bypassed.sort(key=lambda x: x[0])
checks = []
violation_count = len(non_bypassed)
if violation_count == 0:
checks.append(
{
"name": "Windows compat",
"passed": True,
"message": "No unguarded POSIX-only patterns found",
}
)
else:
previews = [f"L{ln}: {desc}" for ln, desc in non_bypassed[:3]]
preview_str = "; ".join(previews)
suffix = f" (and {violation_count - 3} more)" if violation_count > 3 else ""
checks.append(
{
"name": "Windows compat",
"passed": False,
"message": f"{violation_count} unguarded POSIX pattern(s): {preview_str}{suffix}",
}
)
passed_checks = sum(1 for c in checks if c["passed"])
total_checks = len(checks)
score = int((passed_checks / total_checks) * 100) if total_checks > 0 else 0
overall_passed = score >= 75
json_handler.log_operation(
"check_completed",
{"file": str(module_path), "score": score, "standard": "windows_compat"},
)
return {
"passed": overall_passed,
"checks": checks,
"score": score,
"standard": "WINDOWS_COMPAT",
}
@@ -0,0 +1,120 @@
# =================== AIPass ====================
# Name: windows_compat_content.py
# Description: Windows Compatibility Standards Content Handler
# Version: 1.0.0
# Created: 2026-05-10
# Modified: 2026-05-10
# =============================================
"""
Windows Compatibility Standards Content Handler
Provides formatted Windows Compatibility standards content.
Module orchestrates, handler implements.
"""
from aipass.seedgo.apps.handlers.json import json_handler
def get_windows_compat_standards() -> str:
"""Return formatted windows_compat standards content with Rich markup.
Returns:
str: Formatted standards text with Rich styling
"""
lines = [
"[bold cyan]CORE PRINCIPLE:[/bold cyan]",
" Code must run on both Linux and Windows. POSIX-only APIs must be",
" guarded with [yellow]if sys.platform != 'win32'[/yellow] or wrapped in",
" [yellow]try/except ImportError[/yellow]. We ADD Windows code paths alongside",
" existing Linux code -- we NEVER change the Linux path.",
"",
"[bold cyan]WHAT IT CHECKS:[/bold cyan]",
" Parses Python files with [dim]ast.parse()[/dim] and detects:",
"",
" 1. [red]Unguarded POSIX-only imports[/red]: fcntl, pwd, grp, termios, resource",
" 2. [red]Unguarded POSIX-only constants[/red]: os.WNOHANG, signal.SIGPIPE",
" 3. [red]Unguarded POSIX-only calls[/red]: os.fork, os.setpgid, os.killpg,",
" os.getpgid, os.waitpid",
" 4. [red]os.kill() without OSError handling[/red]: Windows raises WinError 87",
"",
" [yellow]Valid guards (checker recognizes):[/yellow]",
" - [dim]if sys.platform != 'win32':[/dim]",
" - [dim]if sys.platform == 'linux':[/dim]",
" - [dim]if os.name != 'nt':[/dim] / [dim]if os.name == 'posix':[/dim]",
" - [dim]try: import fcntl / except ImportError:[/dim]",
" - [dim]if hasattr(signal, 'SIGPIPE'):[/dim]",
" - [dim]try: os.kill(...) / except OSError:[/dim]",
"",
" [yellow]Skips:[/yellow]",
" - [dim]__init__.py[/dim] files",
" - Non-.py files",
"",
"[bold cyan]VIOLATIONS:[/bold cyan]",
"",
" [red]Bad -- bare POSIX import:[/red]",
" [dim]import fcntl[/dim]",
" [dim]fcntl.flock(fd, fcntl.LOCK_EX)[/dim]",
"",
" [red]Bad -- unguarded os.WNOHANG:[/red]",
" [dim]pid, _ = os.waitpid(-1, os.WNOHANG)[/dim]",
"",
" [red]Bad -- os.kill without exception handling:[/red]",
" [dim]os.kill(pid, 0) # WinError 87 on Windows[/dim]",
"",
"[bold cyan]HOW TO FIX:[/bold cyan]",
"",
" [green]Good -- platform-guarded import:[/green]",
' [dim]if sys.platform == "win32":[/dim]',
" [dim] import msvcrt[/dim]",
" [dim]else:[/dim]",
" [dim] import fcntl[/dim]",
"",
" [green]Good -- try/except import:[/green]",
" [dim]try:[/dim]",
" [dim] import fcntl[/dim]",
" [dim]except ImportError:[/dim]",
" [dim] fcntl = None[/dim]",
"",
" [green]Good -- guarded POSIX constant:[/green]",
' [dim]if sys.platform != "win32":[/dim]',
" [dim] pid, _ = os.waitpid(-1, os.WNOHANG)[/dim]",
"",
" [green]Good -- os.kill with OSError catch:[/green]",
" [dim]try:[/dim]",
" [dim] os.kill(pid, 0)[/dim]",
" [dim]except OSError:[/dim]",
" [dim] return False[/dim]",
"",
" [green]Good -- hasattr guard for signal constants:[/green]",
' [dim]if hasattr(signal, "SIGPIPE"):[/dim]',
" [dim] signal.signal(signal.SIGPIPE, signal.SIG_DFL)[/dim]",
"",
"[yellow]SCOPE:[/yellow]",
" AUDIT_SCOPE = [bold]all_files[/bold]",
" Checks every .py file in the branch individually via AST parsing",
"",
"[bold cyan]SCORING:[/bold cyan]",
" One check per file (Windows compat)",
" [green]100[/green] = no unguarded POSIX-only patterns found",
" [red]0[/red] = one or more unguarded patterns found",
" Reports up to 3 offending line numbers with descriptions",
" Overall pass threshold: [yellow]75%[/yellow]",
"",
"[bold cyan]BYPASS:[/bold cyan]",
" Via [dim].seedgo/bypass.json[/dim] -- supports standard, file-level,",
" and line-level bypass rules",
"",
" [dim]Example bypass entry:[/dim]",
' [dim]{{"file": "apps/daemon.py", "standard": "windows_compat",[/dim]',
' [dim] "reason": "Daemon is Linux-only by design"}}[/dim]',
"",
"[bold cyan]REFERENCE:[/bold cyan]",
" [dim]See: seedgo standards pack (windows_compat)[/dim]",
" [dim]Checker: windows_compat_check.py[/dim]",
" [dim]Windows CI: .github/workflows/windows-test.yml[/dim]",
" [dim]Issue: #326 (Input-X Windows platform report)[/dim]",
]
json_handler.log_operation("standard_content_queried", {"standard": "windows_compat"})
return "\n".join(lines)