/** HUX-02 display and scope validation. Raw payloads never cross this boundary. */ import type { HuxIdentity, HuxSurface } from "./types.ts"; const CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g; const AUTH = /\b(password|passwd|token|api[-_ ]?key|secret|authorization|cookie)\b\s*[:=]\s*[^\s,;]+/gi; const BEARER = /\bbearer\s+[A-Za-z0-9._~+/-]+=*/gi; const JWT = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g; const PEM = /-----BEGIN [^-]+-----[\s\S]*?-----END [^-]+-----/gi; const BLOB = /\b(?=[A-Za-z0-9_+/#.-]{40,}\b)(?=[A-Za-z0-9_+/#.-]*[A-Za-z])(?=[A-Za-z0-9_+/#.-]*\d)[A-Za-z0-9_+/#.-]+\b/g; const URI_USERINFO = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi; const SURFACES = new Set([ "chat", "worker", "telegram", "voice", "api", ]); export class MemoryContractError extends Error { constructor(message: string) { super(message); this.name = "MemoryContractError"; } } export function safeMemoryText( value: unknown, fallback: string, maxLength = 280, ): string { if (typeof value !== "string") return fallback; const limit = Math.max(1, maxLength); const safe = value .replace(PEM, "[redacted credential]") .replace(AUTH, "$1=[redacted]") .replace(BEARER, "Bearer [redacted]") .replace(JWT, "[redacted credential]") .replace(URI_USERINFO, "$1[redacted]@") .replace(BLOB, "[redacted credential]") .replace(CONTROL, " ") .replace(/\s+/g, " ") .trim(); if (!safe) return fallback; if (safe.length <= limit) return safe; return limit === 1 ? "…" : `${safe.slice(0, limit - 1).trimEnd()}…`; } export function isOpaqueMemoryId(value: unknown): value is string { return typeof value === "string" && /^mem_[A-Za-z0-9._-]{4,80}$/.test(value); } export function isControlReference( value: unknown, prefix?: "conv", ): value is string { if (typeof value !== "string") return false; if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/.test(value)) return false; return !prefix || value.startsWith(`${prefix}_`); } export function isUtcTimestamp(value: unknown): value is string { return ( typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?Z$/.test(value) && !Number.isNaN(Date.parse(value)) ); } export function normalizeIdentity(value: unknown): HuxIdentity { const raw = value as Record | null; const tenantRef = raw?.tenant_ref ?? raw?.tenantRef; const userRef = raw?.user_ref ?? raw?.userRef; if ( !raw || Array.isArray(raw) || typeof tenantRef !== "string" || !/^tnt_[0-9a-f]{16,64}$/.test(tenantRef) || typeof userRef !== "string" || !/^usr_[0-9a-f]{16,64}$/.test(userRef) || !SURFACES.has(raw.surface as HuxSurface) ) { throw new MemoryContractError("Memory identity could not be verified"); } return { tenantRef, userRef, surface: raw.surface as HuxSurface }; } export function sameIdentity(left: HuxIdentity, right: HuxIdentity): boolean { return ( left.tenantRef === right.tenantRef && left.userRef === right.userRef && left.surface === right.surface ); }