feat: harden chat state by tenant
This commit is contained in:
@@ -35,30 +35,49 @@ HARNESS_BIN = os.path.join(HARNESS_ROOT, "scripts", "bash")
|
||||
ROUTER = os.path.join(HARNESS_BIN, "prompt-mode-router.py")
|
||||
SECURITY = os.path.join(HARNESS_BIN, "security-check.sh")
|
||||
ACTION_GATE = os.path.join(HARNESS_BIN, "action-gate.sh")
|
||||
TENANT_STORE = os.path.join(HARNESS_BIN, "tenant-store.sh")
|
||||
TENANT_CRYPT = os.path.join(HARNESS_BIN, "tenant-crypt.sh")
|
||||
|
||||
|
||||
def state_root() -> str:
|
||||
return os.environ.get("CASAN_STATE_ROOT") or os.path.join(ROOT, ".specify")
|
||||
|
||||
|
||||
def tenant_path(logical: str, fallback: str) -> str:
|
||||
if os.environ.get("CASAN_TENANT_ID"):
|
||||
r = subprocess.run(["bash", TENANT_STORE, "resolve", logical], cwd=ROOT, capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
raise SystemExit((r.stderr or r.stdout or "TENANT_DENIED").strip())
|
||||
return r.stdout.strip()
|
||||
return os.path.join(state_root(), fallback)
|
||||
|
||||
|
||||
def guarded_override(path: str) -> str:
|
||||
if path and os.environ.get("CASAN_TENANT_ID"):
|
||||
r = subprocess.run(["bash", TENANT_STORE, "guard", path], cwd=ROOT, capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
raise SystemExit((r.stderr or r.stdout or "TENANT_DENIED").strip())
|
||||
return path
|
||||
|
||||
|
||||
def config_path() -> str:
|
||||
return os.environ.get("CASAN_OPERATOR_ACTIONS_FILE") or os.path.join(HARNESS_ROOT, "config", "operator-actions.yaml")
|
||||
|
||||
|
||||
def audit_path() -> str:
|
||||
return os.environ.get("CASAN_CHAT_AUDIT_LOG") or os.path.join(state_root(), "logs", "chat", "chat-turns.jsonl")
|
||||
return guarded_override(os.environ["CASAN_CHAT_AUDIT_LOG"]) if os.environ.get("CASAN_CHAT_AUDIT_LOG") else tenant_path("chat/chat-turns.jsonl", "logs/chat/chat-turns.jsonl")
|
||||
|
||||
|
||||
def head_path() -> str:
|
||||
return os.environ.get("CASAN_CHAT_AUDIT_HEAD") or os.path.join(state_root(), "logs", "chat", "chat-head.txt")
|
||||
return guarded_override(os.environ["CASAN_CHAT_AUDIT_HEAD"]) if os.environ.get("CASAN_CHAT_AUDIT_HEAD") else tenant_path("chat/chat-head.txt", "logs/chat/chat-head.txt")
|
||||
|
||||
|
||||
def metrics_path() -> str:
|
||||
return os.environ.get("CASAN_CHAT_METRICS_LOG") or os.path.join(state_root(), "logs", "cost", "metrics.jsonl")
|
||||
return guarded_override(os.environ["CASAN_CHAT_METRICS_LOG"]) if os.environ.get("CASAN_CHAT_METRICS_LOG") else tenant_path("telemetry/cost/metrics.jsonl", "logs/cost/metrics.jsonl")
|
||||
|
||||
|
||||
def artifact_dir() -> str:
|
||||
return os.path.join(state_root(), "logs", "chat", "operator-artifacts")
|
||||
return tenant_path("chat/operator-artifacts", "logs/chat/operator-artifacts")
|
||||
|
||||
|
||||
def now_iso() -> str:
|
||||
@@ -181,9 +200,18 @@ def record_turn(base):
|
||||
os.makedirs(os.path.dirname(head_path()), exist_ok=True)
|
||||
with open(head_path(), "w", encoding="utf-8") as fh:
|
||||
fh.write(record_hash + "\n")
|
||||
encrypt_chat_audit_snapshot(path)
|
||||
return rec
|
||||
|
||||
|
||||
def encrypt_chat_audit_snapshot(path: str):
|
||||
if not os.environ.get("CASAN_TENANT_ID"):
|
||||
return
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
subprocess.run(["bash", TENANT_CRYPT, "encrypt", path, path + ".enc"], cwd=ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
|
||||
def record_metrics(trace_id: str, message: str, answer: str, status: str, latency_ms: int, action_id: str):
|
||||
input_tokens = len(message.split())
|
||||
output_tokens = len(answer.split())
|
||||
@@ -287,6 +315,8 @@ def list_actions(args) -> int:
|
||||
|
||||
|
||||
def run(args) -> int:
|
||||
if args.tenant and args.tenant != "default":
|
||||
os.environ["CASAN_TENANT_ID"] = args.tenant
|
||||
started = datetime.now(timezone.utc)
|
||||
trace_id = str(uuid.uuid4())
|
||||
chat_id = args.chat_id or "chat-default"
|
||||
|
||||
Reference in New Issue
Block a user