atlas-iac/testing/tests/test_hermes_hux_ui_research.mjs

243 lines
14 KiB
JavaScript
Raw Permalink Normal View History

import assert from "node:assert/strict";
import test from "node:test";
import {createResearchEndpointContract} from "../../dockerfiles/hermes-webui-hux/research/endpoints.ts";
import {
FOUNDATION_FLAG, FRIENDLY_MODES_FLAG, RESEARCH_FLAG, normalizeResearchPage, researchEnabled,
} from "../../dockerfiles/hermes-webui-hux/research/model.ts";
import {
asRecord, boundedText, isHash, isId, isUtc, normalizeIdentity, normalizeScope,
safeMessageId, safePathId, safeSourceUri, sameIdentity, sameScope,
} from "../../dockerfiles/hermes-webui-hux/research/security.ts";
const IDENTITY = {tenantRef: "tnt_0123456789abcdef", userRef: "usr_0123456789abcdef", surface: "chat"};
const RAW_IDENTITY = {tenant_ref: IDENTITY.tenantRef, user_ref: IDENTITY.userRef, surface: "chat"};
const SCOPE = {projectId: "prj_project123", conversationId: "conv_converse1"};
const RAW_SCOPE = {project_id: SCOPE.projectId, conversation_id: SCOPE.conversationId};
const HASH = `sha256:${"a".repeat(64)}`;
const FLAGS = [FOUNDATION_FLAG, FRIENDLY_MODES_FLAG, RESEARCH_FLAG];
function source(extra = {}) {
return {schema: "hux.source.v1", id: "src_primary12", kind: "web",
uri: "https://example.com/report", title: "Primary report", publisher: "Example Institute",
published_at: "2026-08-20T10:00:00Z", retrieved_at: "2026-08-24T10:00:00Z",
classification: "primary", content_hash: HASH, provenance: {surface: "chat",
actor: {type: "assistant", id: "hermes"}, recorded_at: "2026-08-24T10:00:01Z",
conversation_id: SCOPE.conversationId, run_id: "run_research"}, ...extra};
}
function passage(extra = {}) {
return {schema: "hux.passage.v1", id: "psg_passage12", source_id: "src_primary12",
text: "The exact supporting passage.\nSecond line.", hash: HASH,
locator: {page: 2, selector: "#finding", line_start: 10, line_end: 12,
char_start: 50, char_end: 91}, ...extra};
}
function citation(extra = {}) {
return {schema: "hux.citation.v1", id: "cit_citation1", message_id: "msg-44",
claim: "The report supports this claim.", passage_ids: ["psg_passage12"],
support: "supports", note: "Check the methodology.", ...extra};
}
function notebook(extra = {}) {
return {schema: "hux.research_notebook.v1", id: "nb_notebook12",
conversation_id: SCOPE.conversationId, question: "What does the evidence show?", status: "open",
source_ids: ["src_primary12"], passage_ids: ["psg_passage12"], citation_ids: ["cit_citation1"],
assumptions: ["The publication date is accurate."], unresolved_questions: ["Was it peer reviewed?"],
updated_at: "2026-08-24T10:00:02Z", ...extra};
}
function page(extra = {}) {
return {schema: "hux.research_page.v1", api_version: "hux.v1", identity: RAW_IDENTITY,
binding: RAW_SCOPE, sources: [source()], passages: [passage()], citations: [citation()],
notebooks: [notebook()], ...extra};
}
test("research stays default-off until its declared capabilities are active", () => {
assert.equal(researchEnabled(), false);
assert.equal(researchEnabled([]), false);
for (const flag of FLAGS) assert.equal(researchEnabled(FLAGS.filter((item) => item !== flag)), false);
assert.equal(researchEnabled(FLAGS), true);
});
test("security helpers enforce exact identity, scope, text, IDs, timestamps and hashes", () => {
assert.deepEqual(asRecord({a: 1}), {a: 1});
assert.equal(asRecord(null), null);
assert.equal(asRecord([]), null);
assert.equal(isId("src_primary12", "src"), true);
assert.equal(isId("psg_passage12", "src"), false);
assert.equal(isId(4), false);
assert.equal(isUtc("2026-08-24T10:00:00.123Z"), true);
assert.equal(isUtc("today"), false);
assert.equal(isUtc(4), false);
assert.equal(isHash(HASH), true);
assert.equal(isHash(`sha256:${"z".repeat(64)}`), false);
assert.equal(isHash(4), false);
assert.equal(boundedText("hello", 1, 5), true);
assert.equal(boundedText("", 1, 5), false);
assert.equal(boundedText("bad\u0000", 1, 10), false);
assert.equal(boundedText(4, 1, 5), false);
assert.deepEqual(normalizeIdentity(RAW_IDENTITY), IDENTITY);
for (const bad of [null, [], {...RAW_IDENTITY, tenant_ref: "bad"},
{...RAW_IDENTITY, user_ref: "bad"}, {...RAW_IDENTITY, surface: "browser"}]) {
assert.equal(normalizeIdentity(bad), null);
}
assert.deepEqual(normalizeScope(RAW_SCOPE), SCOPE);
assert.equal(normalizeScope(null), null);
assert.equal(normalizeScope({...RAW_SCOPE, project_id: "bad"}), null);
assert.equal(normalizeScope({...RAW_SCOPE, conversation_id: "bad"}), null);
assert.equal(sameIdentity(IDENTITY, IDENTITY), true);
assert.equal(sameIdentity(null, IDENTITY), false);
assert.equal(sameIdentity({...IDENTITY, surface: "voice"}, IDENTITY), false);
assert.equal(sameScope(SCOPE, SCOPE), true);
assert.equal(sameScope(null, SCOPE), false);
assert.equal(sameScope({...SCOPE, conversationId: "conv_other1234"}, SCOPE), false);
});
test("source URLs allow HTTPS and HUX paths but reject active or ambiguous schemes", () => {
assert.equal(safeSourceUri(undefined), null);
assert.equal(safeSourceUri(null), null);
assert.equal(safeSourceUri(""), null);
assert.equal(safeSourceUri("https://example.com/a?q=1"), "https://example.com/a?q=1");
assert.equal(safeSourceUri("/hux/v1/artifacts/art_safe1234"), "/hux/v1/artifacts/art_safe1234");
for (const unsafe of ["http://example.com", "javascript:alert(1)", "data:text/html,x",
"//example.com/x", "/hux/v1/../secret", "/hux/v1/x?q=1", "https://u:p@example.com", "not a url",
`https://example.com/${"x".repeat(2000)}`, "https://example.com/\u0000"] ) {
assert.equal(safeSourceUri(unsafe), null);
}
assert.equal(safeMessageId("msg-44"), "msg-44");
assert.throws(() => safeMessageId("bad/path"), TypeError);
assert.throws(() => safeMessageId(""), TypeError);
assert.equal(safePathId("nb_notebook12", "nb"), "nb_notebook12");
assert.throws(() => safePathId("bad", "nb"), TypeError);
});
test("valid research keeps provenance, bounded passages, support, and notebook notes", () => {
const result = normalizeResearchPage(page(), IDENTITY, SCOPE);
assert.equal(result.rejected, 0);
assert.equal(result.sources[0].classification, "primary");
assert.equal(result.sources[0].provenance.actorType, "assistant");
assert.equal(result.sources[0].provenance.runId, "run_research");
assert.equal(result.passages[0].text, "The exact supporting passage.\nSecond line.");
assert.equal(result.passages[0].locator, "page 2 · #finding · lines 1012 · characters 5091");
assert.equal(result.citations[0].support, "supports");
assert.deepEqual(result.notebooks[0].notes, ["Check the methodology."]);
assert.deepEqual(result.notebooks[0].unresolvedQuestions, ["Was it peer reviewed?"]);
});
test("envelope rejects cross-tenant, user, project and conversation data", () => {
const bad = [null, {}, page({schema: "old"}), page({api_version: "hux.v0"}),
page({identity: {...RAW_IDENTITY, tenant_ref: "tnt_ffffffffffffffff"}}),
page({identity: {...RAW_IDENTITY, user_ref: "usr_ffffffffffffffff"}}),
page({identity: {...RAW_IDENTITY, surface: "voice"}}),
page({binding: {...RAW_SCOPE, project_id: "prj_other1234"}}),
page({binding: {...RAW_SCOPE, conversation_id: "conv_other1234"}}),
page({sources: null}), page({sources: Array(501).fill(source())}),
page({passages: null}), page({passages: Array(501).fill(passage())}),
page({citations: null}), page({citations: Array(501).fill(citation())}),
page({notebooks: null}), page({notebooks: Array(101).fill(notebook())})];
for (const raw of bad) assert.equal(normalizeResearchPage(raw, IDENTITY, SCOPE), null);
});
test("invalid source metadata, provenance and URL are withheld with dependants", () => {
const badSources = [null, source({schema: "old"}), source({id: "bad"}), source({kind: "video"}),
source({title: ""}), source({publisher: "x".repeat(201)}), source({retrieved_at: "today"}),
source({classification: "official"}), source({published_at: "today"}), source({content_hash: "bad"}),
source({published_at: "2026-08-25T10:00:00Z"}),
source({uri: "javascript:alert(1)"}), source({provenance: null}),
source({provenance: {...source().provenance, surface: "browser"}}),
source({provenance: {...source().provenance, actor: null}}),
source({provenance: {...source().provenance, actor: {type: "intruder"}}}),
source({provenance: {...source().provenance, actor: {type: "assistant", id: ""}}}),
source({provenance: {...source().provenance, recorded_at: "today"}}),
source({provenance: {...source().provenance, conversation_id: "conv_other1234"}}),
source({provenance: {...source().provenance, run_id: ""}})];
for (const raw of badSources) {
const result = normalizeResearchPage(page({sources: [raw]}), IDENTITY, SCOPE);
assert.equal(result.sources.length, 0);
assert.equal(result.passages.length, 0);
assert.equal(result.citations.length, 0);
assert.equal(result.notebooks.length, 0);
}
const withoutOptional = normalizeResearchPage(page({sources: [source({uri: undefined,
publisher: undefined, published_at: undefined, content_hash: undefined, provenance: undefined})]}), IDENTITY, SCOPE);
assert.equal(withoutOptional.sources[0].uri, null);
assert.equal(withoutOptional.sources[0].provenance, null);
});
test("passage locators and citations are schema-bounded and referentially complete", () => {
const invalidPassages = [null, passage({schema: "old"}), passage({id: "bad"}),
passage({source_id: "src_missing12"}), passage({text: ""}), passage({text: "x".repeat(4001)}),
passage({hash: "bad"}), passage({locator: null}), passage({locator: {unknown: 1}}),
passage({locator: {page: 0}}), passage({locator: {selector: "x".repeat(501)}}),
passage({locator: {line_start: 0}}), passage({locator: {line_end: 0}}),
passage({locator: {char_start: -1}}), passage({locator: {char_end: -1}}),
passage({locator: {line_start: 10, line_end: 9}}), passage({locator: {char_start: 10, char_end: 9}})];
for (const raw of invalidPassages) {
assert.equal(normalizeResearchPage(page({passages: [raw]}), IDENTITY, SCOPE).passages.length, 0);
}
assert.equal(normalizeResearchPage(page({passages: [passage({locator: undefined})]}), IDENTITY, SCOPE)
.passages[0].locator, "");
assert.equal(normalizeResearchPage(page({passages: [passage({locator: {line_start: 4}})]}), IDENTITY, SCOPE)
.passages[0].locator, "lines 4");
const invalidCitations = [null, citation({schema: "old"}), citation({id: "bad"}),
citation({message_id: ""}), citation({claim: ""}), citation({passage_ids: null}),
citation({passage_ids: []}), citation({passage_ids: Array(33).fill("psg_passage12")}),
citation({passage_ids: ["psg_passage12", "psg_passage12"]}),
citation({passage_ids: ["psg_missing12"]}), citation({support: "maybe"}),
citation({note: "x".repeat(501)})];
for (const raw of invalidCitations) {
assert.equal(normalizeResearchPage(page({citations: [raw]}), IDENTITY, SCOPE).citations.length, 0);
}
const allSupport = ["supports", "partially_supports", "contradicts", "unverified"];
for (const support of allSupport) assert.equal(normalizeResearchPage(page({citations: [citation({support})]}),
IDENTITY, SCOPE).citations[0].support, support);
});
test("notebooks validate references, lists, status, conversation and duplicates", () => {
const invalid = [null, notebook({schema: "old"}), notebook({id: "bad"}),
notebook({conversation_id: "conv_other1234"}), notebook({question: ""}), notebook({status: "paused"}),
notebook({updated_at: "today"}), notebook({source_ids: null}), notebook({source_ids: ["src_missing12"]}),
notebook({source_ids: ["src_primary12", "src_primary12"]}), notebook({passage_ids: null}),
notebook({passage_ids: ["psg_missing12"]}), notebook({citation_ids: null}),
notebook({citation_ids: ["cit_missing12"]}), notebook({assumptions: null}),
notebook({assumptions: Array(65).fill("x")}), notebook({assumptions: [""]}),
notebook({unresolved_questions: null}), notebook({unresolved_questions: Array(65).fill("x")}),
notebook({unresolved_questions: [""]})];
for (const raw of invalid) {
assert.equal(normalizeResearchPage(page({notebooks: [raw]}), IDENTITY, SCOPE).notebooks.length, 0);
}
for (const status of ["open", "answered", "abandoned"]) {
assert.equal(normalizeResearchPage(page({notebooks: [notebook({status})]}), IDENTITY, SCOPE)
.notebooks[0].status, status);
}
const duplicate = normalizeResearchPage(page({sources: [source(), source()],
passages: [passage(), passage()], citations: [citation(), citation()], notebooks: [notebook(), notebook()]}),
IDENTITY, SCOPE);
assert.deepEqual([duplicate.sources.length, duplicate.passages.length, duplicate.citations.length,
duplicate.notebooks.length], [1, 1, 1, 1]);
assert.equal(duplicate.rejected, 4);
});
test("endpoint contract is scoped, inert, optimistic, and same-origin delegated", () => {
const client = {apiVersion: "hux.v1", identity: IDENTITY,
endpoint(path) { assert.match(path, /^\/projects\/prj_[^/]+\/conversations\/conv_[^/]+\/research/);
return `/hux/v1${path}`; }};
assert.equal(createResearchEndpointContract(client, SCOPE), null);
assert.equal(createResearchEndpointContract(client, SCOPE, FLAGS.slice(1)), null);
const contract = createResearchEndpointContract(client, SCOPE, FLAGS);
assert.equal(contract.page.path, "/hux/v1/projects/prj_project123/conversations/conv_converse1/research");
assert.match(contract.citationsForMessage("msg-44").path, /messages\/msg-44\/citations$/);
assert.match(contract.notebook("nb_notebook12").path, /notebooks\/nb_notebook12$/);
assert.deepEqual(contract.updateNotebook("nb_notebook12").requiredBody,
["expected_updated_at", "status", "assumptions", "unresolved_questions"]);
assert.deepEqual(contract.attachCitation("msg-44").requiredBody,
["claim", "passage_ids", "support", "note"]);
for (const call of [() => contract.citationsForMessage("bad/path"), () => contract.notebook("bad"),
() => contract.updateNotebook("bad")]) assert.throws(call, TypeError);
assert.throws(() => createResearchEndpointContract({...client, apiVersion: "hux.v2"}, SCOPE, FLAGS), TypeError);
assert.throws(() => createResearchEndpointContract({...client, identity: {...IDENTITY, tenantRef: "bad"}}, SCOPE, FLAGS), TypeError);
assert.throws(() => createResearchEndpointContract(client, {...SCOPE, projectId: "bad"}, FLAGS), TypeError);
});