jenkins b66c762f5d hermes(webui): add HUX card UI models with contract-locked suites
Standalone per-card browser model/security/view modules for HUX-01..10
plus node+pytest suites that read the hux.v1 contract schemas directly.
Reconciled drift found on integration: the activity model now accepts
all 32 hux.event.v1 kinds (delegation.*, memory.suppressed,
memory.retrieval_removed, budget.exhausted, side_effect.*), the
autonomy model carries the external_side_effect capability, and the
foundation boundary test now asserts the shipped static HUX surface
exists on disk and that images never bake activated HUX_FLAGS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 04:12:03 -03:00

97 lines
3.1 KiB
TypeScript

/** 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<HuxSurface>([
"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<string, unknown> | 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
);
}