* fix(ai_mail): resolve relative mailbox_path bug + archive dead handlers Fix get_user_by_email() and get_all_users() returning relative mailbox_path values from registry instead of absolute paths (causing doubled paths in reply). Added path resolution matching get_current_user() pattern. Archive 7 unused handler files to apps/.archive/ (gitignored): - pending_work.py — planned feature, never wired - lock_utils.py — superseded by inbox_lock.py - data_ops.py — consumer (error_monitor) was archived - config_generator.py, users/load.py, registry/validate.py — scaffolded, never used - trigger/error_handler.py — superseded by trigger branch's event system Added test_user_paths.py (13 tests) covering absolute path resolution. Co-Authored-By: @ai_mail <ai_mail@aipass> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(spawn): seedgo compliance cleanup — 93% to 98% Archive legacy files (dev.local.md, ai_mail.local/, .seed/). Update META Modified dates on 22 files. Update README architecture tree and freshness. Fix 15 Pyright unused warnings. Add introspection no-args gates to 6 modules. Wire json_handler + log_operation to 15 operational files. Add seedgo bypass for 5 pure data files and log_structure. Remove deprecated dev.local.md from builder template. Co-Authored-By: @spawn <spawn@aipass> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(system): session 34 — maintenance day, multi-branch work System changes: - setup.sh: symlink install replaces .bashrc PATH export for drone/seedgo - .claude/settings.json: deny list moved to per-branch settings.local.json - .gitignore: updated - CLAUDE.md culture doc: stale refs fixed (Cortex→spawn, id.json→passport.json) Branch work (accumulated from 15-branch rounds): - daemon: stale term cleanup, plugin removals, json handler modernization - drone: module refactor (discovery, registry, router, resolver, config) - commons: 50+ file cleanup (imports, handlers, modules) - flow: template loader, plan type system, command parser Per-branch settings.local.json: reworked deny lists — allow PR workflow, deny destructive git ops (reset, rebase, merge, clean, force push, rm -rf). Fixed pattern syntax from :* to * glob. Co-Authored-By: @devpulse <devpulse@aipass> --------- Co-authored-by: @ai_mail <ai_mail@aipass> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: @spawn <spawn@aipass> Co-authored-by: @devpulse <devpulse@aipass>
108 lines
3.5 KiB
Python
108 lines
3.5 KiB
Python
# ===================AIPASS====================
|
|
# META DATA HEADER
|
|
# Name: __init__.py - The Commons handlers package
|
|
# Date: 2026-03-07
|
|
# Version: 2.0.0
|
|
# Category: commons/apps/handlers
|
|
# =============================================
|
|
|
|
"""Commons handlers package - Security protected."""
|
|
|
|
import inspect
|
|
from pathlib import Path
|
|
|
|
MY_BRANCH = "commons" # Commons is standalone, not under aipass.*
|
|
|
|
|
|
def _find_real_caller():
|
|
"""Walk the stack to find the actual file that triggered this import."""
|
|
stack = inspect.stack()
|
|
this_file = str(Path(__file__).resolve())
|
|
|
|
for frame_info in stack:
|
|
filename = frame_info.filename
|
|
if this_file in str(Path(filename).resolve()):
|
|
continue
|
|
if filename.startswith("<") or "importlib" in filename:
|
|
continue
|
|
import_line = None
|
|
if frame_info.code_context:
|
|
import_line = frame_info.code_context[0].strip()
|
|
return str(Path(filename).resolve()), import_line
|
|
return None, None
|
|
|
|
|
|
def _extract_branch_name(filepath: str) -> str:
|
|
"""Extract branch name from a file path."""
|
|
parts = Path(filepath).parts
|
|
for i, part in enumerate(parts):
|
|
if part == "aipass":
|
|
if i + 1 < len(parts):
|
|
return parts[i + 1]
|
|
# Check for commons specifically
|
|
for i, part in enumerate(parts):
|
|
if part == "commons":
|
|
return "commons"
|
|
return "unknown"
|
|
|
|
|
|
def _guard_branch_access():
|
|
"""Block cross-branch handler imports."""
|
|
caller_file, import_line = _find_real_caller()
|
|
|
|
import os
|
|
|
|
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)
|
|
|
|
if caller_file is None:
|
|
stack = inspect.stack()
|
|
for frame in stack:
|
|
if frame.filename in ("<string>", "<stdin>"):
|
|
target_line = "unknown"
|
|
if frame.code_context:
|
|
target_line = frame.code_context[0].strip()
|
|
raise ImportError(
|
|
f"\n{'='*60}\n"
|
|
f"ACCESS DENIED: Cross-branch handler import blocked\n"
|
|
f"{'='*60}\n"
|
|
f" Caller: interactive/script\n"
|
|
f" Blocked: {target_line}\n"
|
|
f"\n"
|
|
f" Handlers are internal to their branch.\n"
|
|
f" Use the module API instead:\n"
|
|
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
|
|
f"{'='*60}"
|
|
)
|
|
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:
|
|
return # Same branch, allowed
|
|
|
|
caller_branch = _extract_branch_name(caller_file)
|
|
caller_filename = Path(caller_file).name
|
|
blocked_import = import_line if import_line else "unknown"
|
|
|
|
raise ImportError(
|
|
f"\n{'='*60}\n"
|
|
f"ACCESS DENIED: Cross-branch handler import blocked\n"
|
|
f"{'='*60}\n"
|
|
f" Caller branch: {caller_branch}\n"
|
|
f" Caller file: {caller_filename}\n"
|
|
f" Blocked: {blocked_import}\n"
|
|
f"\n"
|
|
f" Handlers are internal to their branch.\n"
|
|
f" Use the module API instead:\n"
|
|
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
|
|
f"{'='*60}"
|
|
)
|
|
|
|
|
|
# Run guard at import time
|
|
_guard_branch_access()
|