From 6aabc8bfe6ebbe935f89c7a7c70d714e96f1aab5 Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Mon, 15 Jun 2026 19:52:31 -0700 Subject: [PATCH] fix(commons,daemon,skills): Windows-compat handler import guard (green CI) The cross-branch import guard's same-branch check used a POSIX-only path test, so on Windows (backslash paths) it failed to recognize a branch importing its OWN handlers -> ImportError at collection, failing all commons + 2 daemon tests on the Windows CI runner. (Unmasked once the pathspec fix let collection proceed.) - commons: '/commons/' substring -> 'commons' in Path(caller_file).parts - daemon + skills: add .replace('\\','/') before the check (matches the idiom already used by 15 other branches' guards) - Convert AIPASS_DEBUG_GUARD debug print() -> sys.stderr.write (cli standard; avoids import-time logger dependency inside the guard) Security semantics unchanged: same-branch allowed, cross-branch still blocked (verified cross-platform). commons+daemon audit 100%, 700 daemon+skills tests pass, commons 449 tests pass. (skills local 99% = untracked skills_json orphans, not in CI.) Co-Authored-By: Claude Opus 4.8 (1M context) --- src/aipass/commons/apps/handlers/__init__.py | 11 ++++++----- .../apps/handlers/artifacts/capsule_ops.py | 16 ++++++++-------- src/aipass/daemon/apps/handlers/__init__.py | 8 ++++---- src/aipass/skills/apps/handlers/__init__.py | 6 +++--- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/aipass/commons/apps/handlers/__init__.py b/src/aipass/commons/apps/handlers/__init__.py index c16f47ec..088183dd 100644 --- a/src/aipass/commons/apps/handlers/__init__.py +++ b/src/aipass/commons/apps/handlers/__init__.py @@ -55,8 +55,8 @@ def _guard_branch_access(): if os.environ.get("AIPASS_DEBUG_GUARD"): import sys - print(f"[GUARD DEBUG] caller_file = {caller_file}", file=sys.stderr) - print(f"[GUARD DEBUG] import_line = {import_line}", file=sys.stderr) + sys.stderr.write(f"[GUARD DEBUG] caller_file = {caller_file}\n") + sys.stderr.write(f"[GUARD DEBUG] import_line = {import_line}\n") if caller_file is None: stack = inspect.stack() @@ -65,9 +65,10 @@ def _guard_branch_access(): return # Allow command-line Python through return - # IMPORTANT: Commons is at src/commons/, not src/aipass/commons/ - # Check if caller is from within the commons directory - if "/commons/" in caller_file: + # Check if caller is from within the commons directory. + # Use path PARTS (not a "/commons/" substring) so the same-branch check + # works on Windows too, where paths use backslash separators. + if "commons" in Path(caller_file).parts: return # Same branch, allowed caller_branch = _extract_branch_name(caller_file) diff --git a/src/aipass/commons/apps/handlers/artifacts/capsule_ops.py b/src/aipass/commons/apps/handlers/artifacts/capsule_ops.py index dcd20a56..8ca395a9 100644 --- a/src/aipass/commons/apps/handlers/artifacts/capsule_ops.py +++ b/src/aipass/commons/apps/handlers/artifacts/capsule_ops.py @@ -27,6 +27,7 @@ from aipass.commons.apps.handlers.json import json_handler # SEAL A TIME CAPSULE # ============================================================================= + def seal_capsule(args: List[str]) -> dict: """ Seal a time capsule that opens after N days. @@ -51,6 +52,7 @@ def seal_capsule(args: List[str]) -> dict: days = max(1, min(365, days)) from aipass.commons.apps.modules.commons_identity import get_caller_branch + caller = get_caller_branch() if not caller: return {"success": False, "error": "Could not detect calling branch. Run from a branch directory."} @@ -64,8 +66,7 @@ def seal_capsule(args: List[str]) -> dict: conn = get_db() cursor = conn.execute( - "INSERT INTO time_capsules (creator, title, content, opens_at) " - "VALUES (?, ?, ?, ?)", + "INSERT INTO time_capsules (creator, title, content, opens_at) VALUES (?, ?, ?, ?)", (creator, title, content, opens_at), ) capsule_id = cursor.lastrowid @@ -91,6 +92,7 @@ def seal_capsule(args: List[str]) -> dict: # LIST TIME CAPSULES # ============================================================================= + def list_capsules(args: List[str]) -> dict: """ List all time capsules with status info. @@ -103,9 +105,7 @@ def list_capsules(args: List[str]) -> dict: try: conn = get_db() - rows = conn.execute( - "SELECT * FROM time_capsules ORDER BY opens_at ASC" - ).fetchall() + rows = conn.execute("SELECT * FROM time_capsules ORDER BY opens_at ASC").fetchall() close_db(conn) @@ -145,6 +145,7 @@ def list_capsules(args: List[str]) -> dict: # OPEN A TIME CAPSULE # ============================================================================= + def open_capsule(args: List[str]) -> dict: """ Open a time capsule if its opens_at date has passed. @@ -164,6 +165,7 @@ def open_capsule(args: List[str]) -> dict: return {"success": False, "error": "Capsule ID must be a number"} from aipass.commons.apps.modules.commons_identity import get_caller_branch + caller = get_caller_branch() if not caller: return {"success": False, "error": "Could not detect calling branch. Run from a branch directory."} @@ -173,9 +175,7 @@ def open_capsule(args: List[str]) -> dict: try: conn = get_db() - row = conn.execute( - "SELECT * FROM time_capsules WHERE id = ?", (capsule_id,) - ).fetchone() + row = conn.execute("SELECT * FROM time_capsules WHERE id = ?", (capsule_id,)).fetchone() if not row: close_db(conn) diff --git a/src/aipass/daemon/apps/handlers/__init__.py b/src/aipass/daemon/apps/handlers/__init__.py index 7ead18d4..45125382 100644 --- a/src/aipass/daemon/apps/handlers/__init__.py +++ b/src/aipass/daemon/apps/handlers/__init__.py @@ -66,8 +66,8 @@ def _guard_branch_access(): if os.environ.get("AIPASS_DEBUG_GUARD"): import sys - print(f"[GUARD DEBUG] caller_file = {caller_file}", file=sys.stderr) - print(f"[GUARD DEBUG] import_line = {import_line}", file=sys.stderr) + sys.stderr.write(f"[GUARD DEBUG] caller_file = {caller_file}\n") + sys.stderr.write(f"[GUARD DEBUG] import_line = {import_line}\n") if caller_file is None: # Can't determine caller from real files @@ -82,8 +82,8 @@ def _guard_branch_access(): # Check if caller is from our branch # MY_BRANCH is "aipass.daemon" (dotted), but filesystem uses "/aipass/daemon/" branch_path = "/" + MY_BRANCH.replace(".", "/") + "/" - if branch_path in caller_file: - return # Same branch, allowed + if branch_path in caller_file.replace("\\", "/"): + return # Same branch, allowed (normalize Windows backslash paths) # External caller - block access caller_branch = _extract_branch_name(caller_file) diff --git a/src/aipass/skills/apps/handlers/__init__.py b/src/aipass/skills/apps/handlers/__init__.py index b5d1e28e..88b24ec7 100644 --- a/src/aipass/skills/apps/handlers/__init__.py +++ b/src/aipass/skills/apps/handlers/__init__.py @@ -66,8 +66,8 @@ def _guard_branch_access(): if os.environ.get("AIPASS_DEBUG_GUARD"): import sys - print(f"[GUARD DEBUG] caller_file = {caller_file}", file=sys.stderr) - print(f"[GUARD DEBUG] import_line = {import_line}", file=sys.stderr) + sys.stderr.write(f"[GUARD DEBUG] caller_file = {caller_file}\n") + sys.stderr.write(f"[GUARD DEBUG] import_line = {import_line}\n") if caller_file is None: stack = inspect.stack() @@ -94,7 +94,7 @@ def _guard_branch_access(): return # Check if caller is from our branch - if f"/{MY_BRANCH}/" in caller_file: + if f"/{MY_BRANCH}/" in caller_file.replace("\\", "/"): return # External caller - block access