48 lines
1.6 KiB
Python
48 lines
1.6 KiB
Python
"""Hermes shared fabric profile isolation primitives."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
import os
|
|
|
|
from .tenant_context import ValidationError, require_ident
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class HermesProfile:
|
|
tenant_id: str
|
|
cell_id: str
|
|
profile_id: str
|
|
hermes_home: Path
|
|
workspace: Path
|
|
|
|
def __post_init__(self) -> None:
|
|
require_ident("tenant_id", self.tenant_id, tenant=True)
|
|
for name in ("cell_id", "profile_id"):
|
|
require_ident(name, getattr(self, name))
|
|
if not self.hermes_home.is_absolute() or not self.workspace.is_absolute():
|
|
raise ValidationError("profile paths must be absolute")
|
|
|
|
@property
|
|
def environment(self) -> dict[str, str]:
|
|
return {
|
|
"HERMES_HOME": str(self.hermes_home),
|
|
"HOME": str(self.hermes_home),
|
|
"AIOS_TENANT_ID": self.tenant_id,
|
|
"AIOS_CELL_ID": self.cell_id,
|
|
"AIOS_PROFILE_ID": self.profile_id,
|
|
}
|
|
|
|
def prepare(self) -> None:
|
|
self.hermes_home.mkdir(parents=True, exist_ok=True)
|
|
self.workspace.mkdir(parents=True, exist_ok=True)
|
|
os.chmod(self.hermes_home, 0o700)
|
|
os.chmod(self.workspace, 0o700)
|
|
|
|
def assert_path_allowed(self, path: str | Path) -> None:
|
|
target = Path(path).resolve()
|
|
for root in (self.hermes_home.resolve(), self.workspace.resolve()):
|
|
if target == root or root in target.parents:
|
|
return
|
|
raise ValidationError("path escapes profile isolation boundary")
|