feat(ai): services partages A2 + SSE streaming B4 + confirmations UI B5 (#79)
CI / lint (push) Successful in 58s
CI / security (push) Successful in 40s
CI / test (push) Successful in 1m15s
CI / build (push) Successful in 37s
CI / e2e (push) Successful in 10m15s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s

This commit is contained in:
2026-09-11 17:06:40 -04:00
parent c55e3e0cbc
commit 4c4e415975
27 changed files with 1465 additions and 386 deletions
+11 -21
View File
@@ -15,6 +15,8 @@ from pathlib import Path
from typing import Any
from backend.auth.middleware import check_vault_access
from backend.services.errors import ServiceError
from backend.services.paths import resolve_safe_path as _resolve_service_path
logger = logging.getLogger("obsigate.tools")
@@ -138,32 +140,20 @@ class ToolContext:
)
def resolve_safe_path(vault_root: Path, relative_path: str) -> Path:
def resolve_safe_path(vault_root: Path, relative_path: str | None) -> Path:
"""Resolve a vault-relative path, rejecting traversal outside the vault.
Mirrors ``backend.main._resolve_safe_path`` but raises a domain error
instead of ``HTTPException`` so the tool layer stays transport-agnostic.
Delegates to the shared :func:`backend.services.paths.resolve_safe_path`
and maps its :class:`ServiceError` to tool domain errors so the tool layer
stays transport-agnostic.
Raises:
ToolPermissionError: When the resolved path escapes the vault root.
ToolError: When the path cannot be resolved.
"""
full_path = vault_root / (relative_path or "")
try:
resolved = full_path.resolve(strict=False)
root = vault_root.resolve(strict=False)
except Exception as e:
raise ToolError(f"Path resolution error: {e}", code="path_error") from e
try:
resolved.relative_to(root)
except ValueError:
# Case-insensitive fallback for Windows / Docker path casing.
if not str(resolved).lower().startswith(str(root).lower()):
logger.warning(f"Path outside vault: vault={root}, requested={relative_path}")
raise ToolPermissionError(
"Access denied: path outside vault",
code="path_outside_vault",
details={"path": relative_path},
) from None
return resolved
return _resolve_service_path(vault_root, relative_path)
except ServiceError as e:
if e.code in ("path_outside_vault", "permission_denied"):
raise ToolPermissionError(e.message, code=e.code, details=e.details) from e
raise ToolError(e.message, code=e.code, details=e.details) from e