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
314 lines
16 KiB
JavaScript
314 lines
16 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import { createAutonomyEndpointContract } from "../../dockerfiles/hermes-webui-hux/autonomy/endpoints.ts";
|
|
import {
|
|
ACTIVITY_TIMELINE_FLAG, AUTONOMY_FLAG, BUDGET_MAX, CAPABILITIES,
|
|
FOUNDATION_FLAG, actionAuthorization, autonomyControlsEnabled,
|
|
buildApprovalDecision, buildPolicyUpdate, buildStopIntent,
|
|
normalizeAutonomyPage, normalizeStopReceipt,
|
|
} from "../../dockerfiles/hermes-webui-hux/autonomy/model.ts";
|
|
import {
|
|
boundedInteger, exactKeys, identityKey, isOpaqueId, isUtc, normalizeIdentity,
|
|
safeText, sameIdentity,
|
|
} from "../../dockerfiles/hermes-webui-hux/autonomy/security.ts";
|
|
|
|
const IDENTITY = {
|
|
tenantRef: "tnt_0123456789abcdef",
|
|
userRef: "usr_0123456789abcdef",
|
|
surface: "chat",
|
|
};
|
|
const CONVERSATION = "conv_test1234";
|
|
const NOW = Date.parse("2026-08-24T10:05:00Z");
|
|
|
|
const grants = (decision = "ask") => CAPABILITIES.map((capability) => ({capability, decision}));
|
|
|
|
function policy(extra = {}) {
|
|
return {
|
|
schema: "hux.policy.v1",
|
|
id: "pol_test1234",
|
|
owner: IDENTITY.userRef,
|
|
scope: {level: "conversation", scope_id: CONVERSATION},
|
|
autonomy: "safe",
|
|
grants: grants(),
|
|
budgets: {
|
|
tokens_per_run: 200000,
|
|
tool_calls_per_run: 40,
|
|
wall_clock_seconds: 900,
|
|
delegations_per_run: 4,
|
|
},
|
|
provenance: {surface: "chat"},
|
|
updated_at: "2026-08-24T10:00:00Z",
|
|
...extra,
|
|
};
|
|
}
|
|
|
|
function approval(id = "apr_test1234", extra = {}) {
|
|
return {
|
|
schema: "hux.approval.v1",
|
|
id,
|
|
run_id: "run_test1234",
|
|
conversation_id: CONVERSATION,
|
|
capability: "send_message",
|
|
request: {summary: "Send the completed report", risk: "high"},
|
|
status: "pending",
|
|
requested_at: "2026-08-24T10:00:00Z",
|
|
expires_at: "2026-08-24T10:10:00Z",
|
|
...extra,
|
|
};
|
|
}
|
|
|
|
function envelope(extra = {}) {
|
|
return {
|
|
schema: "hux.autonomy_page.v1",
|
|
api_version: "hux.v1",
|
|
identity: {tenant_ref: IDENTITY.tenantRef, user_ref: IDENTITY.userRef, surface: "chat"},
|
|
conversation_id: CONVERSATION,
|
|
policy: policy(),
|
|
approvals: [approval()],
|
|
...extra,
|
|
};
|
|
}
|
|
|
|
test("HUX-05 is inert unless all three dependency flags are present", () => {
|
|
assert.equal(autonomyControlsEnabled(), false);
|
|
assert.equal(autonomyControlsEnabled([]), false);
|
|
assert.equal(autonomyControlsEnabled([AUTONOMY_FLAG]), false);
|
|
assert.equal(autonomyControlsEnabled([FOUNDATION_FLAG, AUTONOMY_FLAG]), false);
|
|
assert.equal(autonomyControlsEnabled([FOUNDATION_FLAG, ACTIVITY_TIMELINE_FLAG]), false);
|
|
assert.equal(autonomyControlsEnabled([FOUNDATION_FLAG, ACTIVITY_TIMELINE_FLAG, AUTONOMY_FLAG]), true);
|
|
});
|
|
|
|
test("security helpers reject unscoped identity and redact display text", () => {
|
|
assert.deepEqual(normalizeIdentity({tenant_ref: IDENTITY.tenantRef,
|
|
user_ref: IDENTITY.userRef, surface: "chat"}), IDENTITY);
|
|
assert.deepEqual(normalizeIdentity(IDENTITY), IDENTITY);
|
|
assert.equal(normalizeIdentity(null), null);
|
|
assert.equal(normalizeIdentity([]), null);
|
|
assert.equal(normalizeIdentity({...IDENTITY, tenantRef: "tenant"}), null);
|
|
assert.equal(normalizeIdentity({...IDENTITY, userRef: "brad@example.test"}), null);
|
|
assert.equal(normalizeIdentity({...IDENTITY, surface: "browser"}), null);
|
|
assert.equal(sameIdentity(IDENTITY, {...IDENTITY}), true);
|
|
assert.equal(sameIdentity(IDENTITY, {...IDENTITY, surface: "worker"}), false);
|
|
assert.match(identityKey(IDENTITY), /^tnt_.*:usr_.*:chat$/);
|
|
assert.equal(isOpaqueId("apr_test1234", "apr"), true);
|
|
assert.equal(isOpaqueId("pol_test1234", "apr"), false);
|
|
assert.equal(isOpaqueId(4), false);
|
|
assert.equal(isUtc("2026-08-24T10:00:00.123Z"), true);
|
|
assert.equal(isUtc("2026-99-24T10:00:00Z"), false);
|
|
assert.equal(isUtc(4), false);
|
|
assert.equal(boundedInteger(5, 5), 5);
|
|
assert.equal(boundedInteger(-1, 5), null);
|
|
assert.equal(boundedInteger(6, 5), null);
|
|
assert.equal(boundedInteger(1.5, 5), null);
|
|
assert.equal(exactKeys({b: 1, a: 2}, ["a", "b"]), true);
|
|
assert.equal(exactKeys({a: 1, extra: 2}, ["a"]), false);
|
|
assert.equal(safeText({}, "fallback"), "fallback");
|
|
assert.equal(safeText(" \n ", "fallback"), "fallback");
|
|
assert.equal(safeText("token=secret Bearer abc.def\u0000", "fallback"),
|
|
"token=[redacted] Bearer [redacted]");
|
|
assert.equal(safeText("abcdef", "fallback", 4), "abc…");
|
|
});
|
|
|
|
test("policy and pending approvals normalize against exact identity and scope", () => {
|
|
const page = normalizeAutonomyPage(envelope(), IDENTITY, CONVERSATION, NOW);
|
|
assert.ok(page);
|
|
assert.equal(page.policy.autonomy, "safe");
|
|
assert.deepEqual(page.policy.scope, {level: "conversation", scopeId: CONVERSATION});
|
|
assert.deepEqual(page.policy.budgets, {
|
|
tokensPerRun: 200000, toolCallsPerRun: 40, wallClockSeconds: 900,
|
|
delegationsPerRun: 4,
|
|
});
|
|
assert.equal(page.approvals.length, 1);
|
|
assert.equal(page.approvals[0].risk, "high");
|
|
assert.equal(page.rejectedApprovals, 0);
|
|
|
|
const partial = normalizeAutonomyPage(envelope({approvals: [
|
|
approval(),
|
|
approval("apr_expired1", {expires_at: "2026-08-24T10:04:59Z"}),
|
|
approval("apr_cross123", {conversation_id: "conv_other1234"}),
|
|
approval("apr_secret12", {request: {summary: "token=do-not-show", risk: "low"}}),
|
|
]}), IDENTITY, CONVERSATION, NOW);
|
|
assert.equal(partial.approvals.length, 2);
|
|
assert.equal(partial.rejectedApprovals, 2);
|
|
assert.doesNotMatch(partial.approvals[1].summary, /do-not-show/);
|
|
});
|
|
|
|
test("page normalization rejects malformed envelopes, policies, and budgets", () => {
|
|
const invalidEnvelopes = [
|
|
null,
|
|
{...envelope(), schema: "hux.autonomy_page.v0"},
|
|
{...envelope(), api_version: "hux.v0"},
|
|
{...envelope(), conversation_id: "bad"},
|
|
{...envelope(), identity: {...envelope().identity, user_ref: "usr_ffffffffffffffff"}},
|
|
{...envelope(), identity: null},
|
|
{...envelope(), approvals: {}},
|
|
{...envelope(), approvals: Array(101).fill(approval())},
|
|
{...envelope(), policy: {...policy(), surprise: true}},
|
|
{...envelope(), policy: {...policy(), schema: "old"}},
|
|
{...envelope(), policy: {...policy(), id: "bad"}},
|
|
{...envelope(), policy: {...policy(), owner: "usr_ffffffffffffffff"}},
|
|
{...envelope(), policy: {...policy(), autonomy: "reckless"}},
|
|
{...envelope(), policy: {...policy(), updated_at: "today"}},
|
|
{...envelope(), policy: {...policy(), scope: null}},
|
|
{...envelope(), policy: {...policy(), scope: {level: "global", scope_id: CONVERSATION}}},
|
|
{...envelope(), policy: {...policy(), scope: {level: "project", scope_id: "bad"}}},
|
|
{...envelope(), policy: {...policy(), scope: {level: "planet"}}},
|
|
{...envelope(), policy: {...policy(), grants: {}}},
|
|
{...envelope(), policy: {...policy(), grants: Array(65).fill({capability: "shell", decision: "ask"})}},
|
|
{...envelope(), policy: {...policy(), grants: [{capability: "unknown", decision: "ask"}]}},
|
|
{...envelope(), policy: {...policy(), grants: [{capability: "shell", decision: "maybe"}]}},
|
|
{...envelope(), policy: {...policy(), grants: [{capability: "shell", decision: "ask"}, {capability: "shell", decision: "deny"}]}},
|
|
{...envelope(), policy: {...policy(), budgets: null}},
|
|
{...envelope(), policy: {...policy(), budgets: {unknown: 1}}},
|
|
{...envelope(), policy: {...policy(), budgets: {tokens_per_run: BUDGET_MAX.tokensPerRun + 1}}},
|
|
];
|
|
for (const value of invalidEnvelopes) {
|
|
assert.equal(normalizeAutonomyPage(value, IDENTITY, CONVERSATION, NOW), null);
|
|
}
|
|
});
|
|
|
|
test("approval rows fail closed when malformed, resolved, stale, or reordered", () => {
|
|
const invalid = [
|
|
null,
|
|
{...approval(), extra: true},
|
|
{...approval(), schema: "old"},
|
|
{...approval(), id: "bad"},
|
|
{...approval(), run_id: ""},
|
|
{...approval(), run_id: "x".repeat(121)},
|
|
{...approval(), capability: "unknown"},
|
|
{...approval(), status: "approved"},
|
|
{...approval(), requested_at: "today"},
|
|
{...approval(), expires_at: "today"},
|
|
{...approval(), requested_at: "2026-08-24T10:11:00Z"},
|
|
{...approval(), request: null},
|
|
{...approval(), request: {summary: "x", risk: "extreme"}},
|
|
{...approval(), request: {summary: "x", risk: "low", injected: true}},
|
|
];
|
|
const page = normalizeAutonomyPage(envelope({approvals: invalid}), IDENTITY, CONVERSATION, NOW);
|
|
assert.deepEqual(page.approvals, []);
|
|
assert.equal(page.rejectedApprovals, invalid.length);
|
|
});
|
|
|
|
test("policy updates preserve canonical budgets and require bounded spend and scope", () => {
|
|
const page = normalizeAutonomyPage(envelope(), IDENTITY, CONVERSATION, NOW);
|
|
const draft = {...page.policy.budgets, spendCentsPerRun: 2500, scopeLimit: "conversation"};
|
|
const intent = buildPolicyUpdate(page.policy, IDENTITY, "autonomous", page.policy.grants, draft);
|
|
assert.equal(intent.expectedPolicyId, page.policy.id);
|
|
assert.equal(intent.guardrails.spendCentsPerRun, 2500);
|
|
assert.equal("spendCentsPerRun" in intent.budgets, false);
|
|
assert.deepEqual(Object.keys(intent.grants), CAPABILITIES);
|
|
const invalidDrafts = [
|
|
{...draft, tokensPerRun: -1}, {...draft, toolCallsPerRun: 501},
|
|
{...draft, wallClockSeconds: 86401}, {...draft, delegationsPerRun: 33},
|
|
{...draft, spendCentsPerRun: 100001}, {...draft, scopeLimit: "internet"},
|
|
];
|
|
for (const bad of invalidDrafts) assert.throws(() =>
|
|
buildPolicyUpdate(page.policy, IDENTITY, "safe", page.policy.grants, bad), /bounds/);
|
|
assert.throws(() => buildPolicyUpdate(page.policy, {...IDENTITY, userRef: "usr_ffffffffffffffff"},
|
|
"safe", page.policy.grants, draft), /bounds/);
|
|
assert.throws(() => buildPolicyUpdate(page.policy, IDENTITY, "turbo", page.policy.grants, draft), /bounds/);
|
|
assert.throws(() => buildPolicyUpdate(page.policy, IDENTITY, "safe",
|
|
{...page.policy.grants, shell: "maybe"}, draft), /bounds/);
|
|
});
|
|
|
|
test("authorization requires exact live approval whenever policy says ask", () => {
|
|
const page = normalizeAutonomyPage(envelope(), IDENTITY, CONVERSATION, NOW);
|
|
const pending = page.approvals[0];
|
|
assert.equal(actionAuthorization(null, "read_files", null, IDENTITY, NOW), "deny");
|
|
assert.equal(actionAuthorization({...page.policy, owner: "usr_ffffffffffffffff"},
|
|
"read_files", null, IDENTITY, NOW), "deny");
|
|
assert.equal(actionAuthorization(page.policy, "unknown", null, IDENTITY, NOW), "deny");
|
|
assert.equal(actionAuthorization(page.policy, "send_message", null, IDENTITY, NOW), "approval_required");
|
|
assert.equal(actionAuthorization(page.policy, "send_message", pending, IDENTITY, NOW), "approval_required");
|
|
assert.equal(actionAuthorization(page.policy, "shell", pending, IDENTITY, NOW), "approval_required");
|
|
assert.equal(actionAuthorization(page.policy, "send_message", {...pending, status: "denied"}, IDENTITY, NOW), "approval_required");
|
|
assert.equal(actionAuthorization(page.policy, "send_message", {...pending, expiresAt: "2026-08-24T10:04:00Z"}, IDENTITY, NOW), "approval_required");
|
|
assert.equal(actionAuthorization(page.policy, "send_message", {...pending, identityKey: "cross"}, IDENTITY, NOW), "approval_required");
|
|
const allow = {...page.policy, autonomy: "autonomous",
|
|
grants: {...page.policy.grants, read_files: "allow", send_message: "allow"}};
|
|
assert.equal(actionAuthorization(allow, "read_files", null, IDENTITY, NOW), "allow");
|
|
assert.equal(actionAuthorization(allow, "send_message", null, IDENTITY, NOW), "allow");
|
|
const safe = {...allow, autonomy: "safe"};
|
|
assert.equal(actionAuthorization(safe, "read_files", null, IDENTITY, NOW), "allow");
|
|
assert.equal(actionAuthorization(safe, "send_message", null, IDENTITY, NOW), "approval_required");
|
|
assert.equal(actionAuthorization({...allow, grants: {...allow.grants, deploy: "deny"}},
|
|
"deploy", null, IDENTITY, NOW), "deny");
|
|
});
|
|
|
|
test("approval decision proves identity and original timestamp", () => {
|
|
const item = normalizeAutonomyPage(envelope(), IDENTITY, CONVERSATION, NOW).approvals[0];
|
|
for (const choice of ["once", "session", "always", "deny"]) {
|
|
const intent = buildApprovalDecision(item, choice, IDENTITY, item.requestedAt, NOW);
|
|
assert.equal(intent.choice, choice);
|
|
assert.equal(intent.expectedRunId, item.runId);
|
|
assert.equal(intent.expectedConversationId, CONVERSATION);
|
|
}
|
|
assert.throws(() => buildApprovalDecision(item, "once", IDENTITY, "2026-08-24T10:00:01Z", NOW), /denied/);
|
|
assert.throws(() => buildApprovalDecision(item, "once", {...IDENTITY, surface: "worker"}, item.requestedAt, NOW), /denied/);
|
|
assert.throws(() => buildApprovalDecision({...item, status: "denied"}, "once", IDENTITY, item.requestedAt, NOW), /denied/);
|
|
assert.throws(() => buildApprovalDecision({...item, expiresAt: "2026-08-24T10:04:00Z"}, "once", IDENTITY, item.requestedAt, NOW), /denied/);
|
|
assert.throws(() => buildApprovalDecision(item, "later", IDENTITY, item.requestedAt, NOW), /denied/);
|
|
});
|
|
|
|
test("stop intent and receipt require exact ownership and remain bounded", () => {
|
|
const run = {runId: "run_test1234", conversationId: CONVERSATION,
|
|
tenantRef: IDENTITY.tenantRef, owner: IDENTITY.userRef};
|
|
assert.deepEqual(buildStopIntent(run, IDENTITY, run.runId), {
|
|
runId: run.runId, conversationId: CONVERSATION, identity: IDENTITY,
|
|
});
|
|
for (const bad of [
|
|
{...run, runId: ""}, {...run, runId: "x".repeat(121)},
|
|
{...run, conversationId: "bad"}, {...run, owner: "usr_ffffffffffffffff"},
|
|
{...run, tenantRef: "tnt_ffffffffffffffff"},
|
|
]) assert.throws(() => buildStopIntent(bad, IDENTITY, run.runId), /ownership/);
|
|
assert.throws(() => buildStopIntent(run, IDENTITY, "run_stale1234"), /ownership/);
|
|
|
|
const raw = {
|
|
schema: "hux.cancel_receipt.v1", id: "rcpt_test1234", run_id: run.runId,
|
|
requested_by: {type: "user", id: IDENTITY.userRef},
|
|
requested_at: "2026-08-24T10:05:00Z", outcome: "cancelled",
|
|
side_effects: Array.from({length: 7}, (_, index) => ({
|
|
description: index ? `Effect ${index}` : "token=do-not-show", reverted: index % 2 === 0,
|
|
})),
|
|
};
|
|
const receipt = normalizeStopReceipt(raw, run, IDENTITY);
|
|
assert.equal(receipt.effects.length, 4);
|
|
assert.equal(receipt.omittedEffects, 3);
|
|
assert.equal(receipt.stopped, 2);
|
|
assert.equal(receipt.remaining, 2);
|
|
assert.doesNotMatch(JSON.stringify(receipt), /do-not-show/);
|
|
assert.equal(normalizeStopReceipt({...raw, run_id: "run_other1234"}, run, IDENTITY), null);
|
|
assert.equal(normalizeStopReceipt({...raw, requested_by: null}, run, IDENTITY), null);
|
|
assert.equal(normalizeStopReceipt({...raw, requested_by: {type: "system", id: IDENTITY.userRef}}, run, IDENTITY), null);
|
|
assert.equal(normalizeStopReceipt({...raw, requested_by: {type: "user", id: "usr_ffffffffffffffff"}}, run, IDENTITY), null);
|
|
assert.equal(normalizeStopReceipt({...raw, schema: "old"}, run, IDENTITY), null);
|
|
assert.equal(normalizeStopReceipt(raw, {...run, owner: "usr_ffffffffffffffff"}, IDENTITY), null);
|
|
assert.equal(normalizeStopReceipt(raw, {...run, tenantRef: "tnt_ffffffffffffffff"}, IDENTITY), null);
|
|
});
|
|
|
|
test("endpoint adapter is inert, same-origin, versioned, and release-aware", () => {
|
|
const enabled = new Set([FOUNDATION_FLAG, ACTIVITY_TIMELINE_FLAG, AUTONOMY_FLAG]);
|
|
const client = {
|
|
apiVersion: "hux.v1", identity: IDENTITY,
|
|
enabled: (flag) => enabled.has(flag), endpoint: (path) => `/hux/v1${path}`,
|
|
};
|
|
const contract = createAutonomyEndpointContract(client);
|
|
assert.equal(contract.policy.path, "/hux/v1/autonomy/policy");
|
|
assert.equal(contract.updatePolicy.responseSchema, "hux.policy.v1");
|
|
assert.equal(contract.approvals.path, "/hux/v1/approvals?status=pending&limit=100");
|
|
assert.match(contract.decideApproval("apr_test1234").path, /\/approvals\/apr_test1234\/decision$/);
|
|
assert.match(contract.stop("run_test1234").path, /\/runs\/run_test1234\/stop$/);
|
|
assert.deepEqual(contract.release, {schema: "hux.release.v1", requiredState: "live_verified",
|
|
featureFlags: [FOUNDATION_FLAG, ACTIVITY_TIMELINE_FLAG, AUTONOMY_FLAG]});
|
|
assert.throws(() => contract.decideApproval("bad"), /Opaque/);
|
|
assert.throws(() => contract.stop("bad"), /Exact/);
|
|
for (const flag of enabled) {
|
|
assert.equal(createAutonomyEndpointContract({...client, enabled: (candidate) =>
|
|
candidate !== flag && enabled.has(candidate)}), null);
|
|
}
|
|
assert.equal(createAutonomyEndpointContract({...client, apiVersion: "hux.v0"}), null);
|
|
assert.throws(() => createAutonomyEndpointContract({...client,
|
|
identity: {...IDENTITY, userRef: "raw-email"}}), /identity/);
|
|
});
|