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

47 lines
2.4 KiB
TypeScript

/** Same-origin endpoint descriptors for the future HUX-08 backend. */
import { researchEnabled } from "./model.ts";
import { normalizeIdentity, normalizeScope, safeMessageId, safePathId, sameIdentity, sameScope } from "./security.ts";
import type { HuxIdentity, ResearchEndpoint, ResearchEndpointContract, ResearchScope } from "./types.ts";
interface FoundationClient {
apiVersion: string;
identity: HuxIdentity;
endpoint(path: string): string;
}
function endpoint(method: ResearchEndpoint["method"], path: string, responseSchema: string,
requiredBody?: readonly string[]): ResearchEndpoint {
return Object.freeze({method, path, responseSchema, ...(requiredBody ? {requiredBody} : {})});
}
/** Describes authenticated endpoints without issuing network requests. */
export function createResearchEndpointContract(client: FoundationClient, scope: ResearchScope,
flags?: Iterable<string>): ResearchEndpointContract | null {
if (!researchEnabled(flags)) return null;
const rawIdentity = {tenant_ref: client.identity.tenantRef, user_ref: client.identity.userRef,
surface: client.identity.surface};
const rawScope = {project_id: scope.projectId, conversation_id: scope.conversationId};
if (client.apiVersion !== "hux.v1" || !sameIdentity(normalizeIdentity(rawIdentity), client.identity) ||
!sameScope(normalizeScope(rawScope), scope)) throw new TypeError("Scoped HUX v1 research client required");
const base = client.endpoint(`/projects/${safePathId(scope.projectId, "prj")}` +
`/conversations/${safePathId(scope.conversationId, "conv")}/research`);
return Object.freeze({
page: endpoint("GET", base, "hux.research_page.v1"),
citationsForMessage(messageId) {
return endpoint("GET", `${base}/messages/${safeMessageId(messageId)}/citations`, "hux.citation_page.v1");
},
notebook(notebookId) {
return endpoint("GET", `${base}/notebooks/${safePathId(notebookId, "nb")}`, "hux.research_notebook.v1");
},
updateNotebook(notebookId) {
return endpoint("PUT", `${base}/notebooks/${safePathId(notebookId, "nb")}`, "hux.research_notebook.v1",
["expected_updated_at", "status", "assumptions", "unresolved_questions"]);
},
attachCitation(messageId) {
return endpoint("POST", `${base}/messages/${safeMessageId(messageId)}/citations`, "hux.citation.v1",
["claim", "passage_ids", "support", "note"]);
},
});
}