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
97 lines
3.1 KiB
TypeScript
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
|
|
);
|
|
}
|