atlas-iac/testing/tests/test_hermes_hux_ui_activity.mjs
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

413 lines
12 KiB
JavaScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
ACTIVITY_TIMELINE_FLAG,
FOUNDATION_FLAG,
activityTimelineEnabled,
mergeActivityEvents,
normalizeCancellationReceipt,
surfacePolicy,
} from "../../dockerfiles/hermes-webui-hux/activity/model.ts";
import {
isOpaqueId,
isRfc3339Utc,
safeEvidenceLabel,
safeText,
} from "../../dockerfiles/hermes-webui-hux/activity/security.ts";
const CONVERSATION = "conv_test1234";
const KINDS = JSON.parse(
readFileSync("services/hermes/contracts/hux/event.schema.json"),
).properties.kind.enum;
function event(seq, kind = "tool.call", extra = {}) {
return {
schema: "hux.event.v1",
id: `evt_test${String(seq).padStart(4, "0")}`,
seq,
ts: `2026-08-24T10:${String(Math.floor(seq / 60)).padStart(2, "0")}:${String(seq % 60).padStart(2, "0")}Z`,
conversation_id: CONVERSATION,
kind,
summary: `Safe update ${seq}`,
evidence: [],
provenance: { surface: "chat" },
sensitivity: "personal",
redaction: { level: "none" },
...extra,
};
}
test("feature remains default-off and depends on the foundation", () => {
assert.equal(activityTimelineEnabled(), false);
assert.equal(activityTimelineEnabled([]), false);
assert.equal(activityTimelineEnabled([ACTIVITY_TIMELINE_FLAG]), false);
assert.equal(activityTimelineEnabled([FOUNDATION_FLAG]), false);
assert.equal(
activityTimelineEnabled([FOUNDATION_FLAG, ACTIVITY_TIMELINE_FLAG]),
true,
);
});
test("Chat policy is intentionally calmer and every returned policy is isolated", () => {
const chat = surfacePolicy("chat");
const worker = surfacePolicy("worker");
assert.ok(chat.maxItems < worker.maxItems);
assert.ok(chat.initialItems < worker.initialItems);
assert.ok(chat.evidenceLimit < worker.evidenceLimit);
assert.equal(chat.expandRoutineEvents, false);
assert.equal(worker.expandRoutineEvents, true);
chat.maxItems = 1;
assert.equal(surfacePolicy("chat").maxItems, 80);
assert.deepEqual(surfacePolicy("api"), worker);
assert.ok(
surfacePolicy("voice").maxItems < surfacePolicy("telegram").maxItems,
);
});
test("plain-text security redacts credential shapes without serializing objects", () => {
assert.equal(safeText({ token: "nope" }, "fallback"), "fallback");
assert.equal(safeText(" \n\t ", "fallback"), "fallback");
assert.equal(safeText("abc\u0000def", "fallback"), "abc def");
assert.equal(safeText("abcdef", "fallback", 4), "abc…");
assert.equal(safeText("abcdef", "fallback", 0), "…");
const unsafe = [
"password=hunter2",
"Authorization: Basic-abcd",
"Bearer abc.DEF_123",
"eyJabcdefgh.abcdefghijkl.abcdefghijkl",
"https://user:pass@example.test/path",
"Abcdefghijklmnopqrstuvwxyz1234567890ABCD",
"-----BEGIN PRIVATE KEY----- top secret -----END PRIVATE KEY-----",
].join(" ");
const scrubbed = safeText(unsafe, "fallback", 1000);
assert.doesNotMatch(
scrubbed,
/hunter2|Basic-abcd|abc\.DEF_123|user:pass|top secret|1234567890ABCD/,
);
assert.match(scrubbed, /redacted/);
});
test("only allow-listed evidence labels cross the display boundary", () => {
assert.deepEqual(safeEvidenceLabel("tool_result"), {
kind: "tool_result",
label: "Tool result",
});
assert.deepEqual(safeEvidenceLabel("url"), { kind: "url", label: "Website" });
assert.equal(safeEvidenceLabel("raw_tool_arguments"), null);
assert.equal(safeEvidenceLabel(7), null);
assert.equal(isOpaqueId("evt_abcd", "evt"), true);
assert.equal(isOpaqueId("evt_abcd"), true);
assert.equal(isOpaqueId("mem_abcd", "evt"), false);
assert.equal(isOpaqueId("not opaque"), false);
assert.equal(isOpaqueId(7), false);
assert.equal(isRfc3339Utc("2026-08-24T10:00:00.123Z"), true);
assert.equal(isRfc3339Utc("today"), false);
assert.equal(isRfc3339Utc("2026-99-99T10:00:00Z"), false);
assert.equal(isRfc3339Utc(7), false);
});
test("every contract event maps to a compact semantic phase", () => {
const records = KINDS.map((kind, seq) =>
event(seq, kind, {
parent_event_id: kind === "run.started" ? "evt_parent1234" : undefined,
}),
);
const result = mergeActivityEvents([], records, CONVERSATION, "worker");
assert.equal(result.invalid, 0);
assert.equal(result.items.length, KINDS.length);
const phases = new Set(result.items.map((item) => item.phase));
assert.deepEqual(
phases,
new Set([
"intent",
"action",
"evidence",
"delegation",
"decision",
"completion",
"failure",
]),
);
assert.equal(
result.items.find((item) => item.kind === "run.failed").tone,
"warning",
);
assert.equal(
result.items.find((item) => item.kind === "run.completed").tone,
"success",
);
assert.equal(
result.items.find((item) => item.kind === "approval.requested").tone,
"warning",
);
assert.equal(
result.items.find((item) => item.kind === "tool.call").routine,
true,
);
assert.equal(
result.items.find((item) => item.kind === "message.user").routine,
false,
);
});
test("root runs are actions while child runs are explicit delegations", () => {
const result = mergeActivityEvents(
[],
[
event(1, "run.started"),
event(2, "run.started", { parent_event_id: "evt_parent1234" }),
],
CONVERSATION,
"chat",
);
assert.deepEqual(
result.items.map((item) => item.phase),
["action", "delegation"],
);
assert.deepEqual(
result.items.map((item) => item.tone),
["active", "active"],
);
});
test("normalization redacts summaries and never projects detail, IDs, or URIs", () => {
const raw = event(1, "tool.result", {
summary: "token=secret-value result is ready",
run_id: "run-safe",
detail: { arguments: "--token secret", result: "raw secret result" },
evidence: [
{
kind: "tool_result",
id: "secret-id",
uri: "https://user:pass@example.test",
},
{ kind: "file", id: "/secret/path" },
{ kind: "bogus", id: "raw" },
{ kind: "url", id: "https://secret" },
],
});
const item = mergeActivityEvents([], [raw], CONVERSATION, "chat").items[0];
const serialized = JSON.stringify(item);
assert.equal(item.runId, "run-safe");
assert.deepEqual(
item.evidence.map((entry) => entry.label),
["Tool result", "File", "Website"],
);
assert.doesNotMatch(
serialized,
/secret-value|raw secret result|secret-id|secret\/path|user:pass/,
);
assert.equal("detail" in item, false);
const invalidRun = mergeActivityEvents(
[],
[event(2, "tool.call", { run_id: { raw: true } })],
CONVERSATION,
"chat",
);
assert.equal(invalidRun.items[0].runId, undefined);
});
test("full or restricted redaction ignores even an otherwise safe summary", () => {
const result = mergeActivityEvents(
[],
[
event(1, "tool.call", {
summary: "do not show",
redaction: { level: "full" },
}),
event(2, "run.failed", {
summary: "health detail",
sensitivity: "restricted",
}),
event(3, "run.completed", { summary: {}, redaction: null }),
event(4, "citation.attached", {
summary: "",
redaction: { level: "partial" },
}),
],
CONVERSATION,
"chat",
);
assert.deepEqual(
result.items.map((item) => item.summary),
[
"Hermes used a tool",
"The run failed; sensitive detail is hidden",
"The run completed",
"Activity recorded; sensitive detail is hidden",
],
);
});
test("reconnect merge is ordered, idempotent, and fail-closed on conflicts", () => {
const first = mergeActivityEvents(
[],
[event(3), event(1)],
CONVERSATION,
"chat",
);
const replay = { ...event(3), summary: "attempted rewrite" };
const idConflict = { ...event(4), id: first.items[0].id };
const seqConflict = { ...event(1), id: "evt_conflict1" };
const result = mergeActivityEvents(
first.items,
[replay, idConflict, seqConflict, event(2)],
CONVERSATION,
"chat",
);
assert.deepEqual(
result.items.map((item) => item.seq),
[1, 2, 3],
);
assert.equal(result.items.at(-1).summary, "Safe update 3");
assert.equal(result.replays, 1);
assert.equal(result.conflicts, 2);
assert.equal(result.afterSeq, 3);
assert.equal(result.invalid, 0);
});
test("an initial same-sequence conflict resolves deterministically", () => {
const higher = { ...event(7), id: "evt_zulu1234" };
const lower = {
...event(7),
id: "evt_alpha1234",
summary: "Deterministic owner",
};
const forward = mergeActivityEvents(
[],
[higher, lower],
CONVERSATION,
"chat",
);
const reverse = mergeActivityEvents(
[],
[lower, higher],
CONVERSATION,
"chat",
);
assert.deepEqual(forward.items, reverse.items);
assert.equal(forward.items[0].id, "evt_alpha1234");
assert.equal(forward.conflicts, 1);
});
test("malformed and cross-conversation events are rejected", () => {
const bad = [
{ ...event(1), schema: "hux.event.v0" },
{ ...event(2), id: "bad" },
{ ...event(3), seq: -1 },
{ ...event(4), ts: "later" },
{ ...event(5), conversation_id: "conv_other1234" },
{ ...event(6), kind: "tool.raw" },
];
const result = mergeActivityEvents([], bad, CONVERSATION, "chat");
assert.deepEqual(result.items, []);
assert.equal(result.invalid, bad.length);
assert.equal(result.afterSeq, -1);
});
test("retention is bounded per surface while preserving the resume watermark", () => {
const records = Array.from({ length: 85 }, (_, seq) => event(seq));
const result = mergeActivityEvents([], records, CONVERSATION, "chat");
assert.equal(result.items.length, 80);
assert.equal(result.items[0].seq, 5);
assert.equal(result.afterSeq, 84);
assert.equal(result.truncated, 5);
const replayed = mergeActivityEvents(
result.items,
[event(84)],
CONVERSATION,
"chat",
);
assert.equal(replayed.replays, 1);
assert.equal(replayed.truncated, 0);
});
test("evidence display and cancellation receipts obey surface bounds", () => {
const evidence = Array.from({ length: 10 }, (_, index) => ({
kind: index % 2 ? "file" : "source",
id: `secret-${index}`,
uri: `https://token-${index}`,
}));
assert.equal(
mergeActivityEvents(
[],
[event(1, "citation.attached", { evidence })],
CONVERSATION,
"chat",
).items[0].evidence.length,
3,
);
assert.equal(
mergeActivityEvents(
[],
[event(1, "citation.attached", { evidence })],
CONVERSATION,
"worker",
).items[0].evidence.length,
8,
);
const receipt = {
schema: "hux.cancel_receipt.v1",
id: "rcpt_test1234",
run_id: "run_1",
requested_at: "2026-08-24T10:00:00Z",
acknowledged_at: "2026-08-24T10:00:01Z",
completed_at: "not-a-time",
outcome: "cancelled",
side_effects: Array.from({ length: 7 }, (_, index) => ({
description: index === 0 ? "token=do-not-show" : `Effect ${index}`,
reverted: index % 2 === 0,
evidence: { kind: "file", id: "secret" },
})),
};
const safe = normalizeCancellationReceipt(receipt, "chat");
assert.equal(safe.sideEffects.length, 4);
assert.equal(safe.omittedEffects, 3);
assert.equal(safe.completedAt, undefined);
assert.equal(safe.acknowledgedAt, receipt.acknowledged_at);
assert.doesNotMatch(JSON.stringify(safe), /do-not-show|"evidence"|"secret"/);
});
test("cancellation parsing rejects bad identity, time, run, and outcome", () => {
const base = {
schema: "hux.cancel_receipt.v1",
id: "rcpt_test1234",
run_id: "run_1",
requested_at: "2026-08-24T10:00:00Z",
outcome: "already_complete",
side_effects: [{ description: {}, reverted: false }],
};
const safe = normalizeCancellationReceipt(base, "worker");
assert.equal(safe.sideEffects[0].description, "A side effect was recorded");
assert.equal(safe.completedAt, undefined);
assert.equal(
normalizeCancellationReceipt({ ...base, schema: "old" }, "chat"),
null,
);
assert.equal(
normalizeCancellationReceipt({ ...base, id: "bad" }, "chat"),
null,
);
assert.equal(
normalizeCancellationReceipt({ ...base, run_id: 3 }, "chat"),
null,
);
assert.equal(
normalizeCancellationReceipt({ ...base, requested_at: "bad" }, "chat"),
null,
);
assert.equal(
normalizeCancellationReceipt({ ...base, outcome: "unknown" }, "chat"),
null,
);
assert.equal(
normalizeCancellationReceipt({ ...base, side_effects: null }, "chat")
.sideEffects.length,
0,
);
});