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
49 lines
2.6 KiB
TypeScript
49 lines
2.6 KiB
TypeScript
/** Same-origin endpoint descriptors for the future HUX-05 backend. */
|
|
|
|
import { ACTIVITY_TIMELINE_FLAG, AUTONOMY_FLAG, FOUNDATION_FLAG } from "./model.ts";
|
|
import { isOpaqueId, normalizeIdentity, sameIdentity } from "./security.ts";
|
|
import type { AutonomyEndpoint, AutonomyEndpointContract, HuxIdentity } from "./types.ts";
|
|
|
|
interface FoundationClient {
|
|
apiVersion: string;
|
|
identity: HuxIdentity;
|
|
enabled(flag: string): boolean;
|
|
endpoint(path: string): string;
|
|
}
|
|
|
|
function endpoint(method: AutonomyEndpoint["method"], path: string,
|
|
responseSchema: string, requiredBody?: readonly string[]): AutonomyEndpoint {
|
|
return Object.freeze({method, path, responseSchema, ...(requiredBody ? {requiredBody} : {})});
|
|
}
|
|
|
|
/** Describe, but never call, the tenant-scoped HUX-05 interface. */
|
|
export function createAutonomyEndpointContract(client: FoundationClient): AutonomyEndpointContract | null {
|
|
if (client.apiVersion !== "hux.v1" ||
|
|
![FOUNDATION_FLAG, ACTIVITY_TIMELINE_FLAG, AUTONOMY_FLAG].every((flag) => client.enabled(flag))) return null;
|
|
const identity = normalizeIdentity(client.identity);
|
|
if (!identity || !sameIdentity(identity, client.identity)) throw new TypeError("Scoped HUX identity required");
|
|
const scoped = (root: string, id: string, prefix: string): string => {
|
|
if (!isOpaqueId(id, prefix)) throw new TypeError(`Opaque ${prefix} id required`);
|
|
return client.endpoint(`${root}/${encodeURIComponent(id)}`);
|
|
};
|
|
return Object.freeze({
|
|
identity: Object.freeze({...identity}),
|
|
policy: endpoint("GET", client.endpoint("/autonomy/policy"), "hux.policy.v1"),
|
|
updatePolicy: endpoint("PUT", client.endpoint("/autonomy/policy"), "hux.policy.v1",
|
|
["expected_policy_id", "expected_updated_at", "autonomy", "grants", "budgets", "guardrails"]),
|
|
approvals: endpoint("GET", client.endpoint("/approvals?status=pending&limit=100"), "hux.approval_page.v1"),
|
|
decideApproval(approvalId: string) {
|
|
return endpoint("POST", `${scoped("/approvals", approvalId, "apr")}/decision`, "hux.approval.v1",
|
|
["expected_run_id", "expected_conversation_id", "expected_capability", "expected_requested_at", "choice"]);
|
|
},
|
|
stop(runId: string) {
|
|
if (!/^run_[A-Za-z0-9._-]{1,116}$/.test(runId)) throw new TypeError("Exact run id required");
|
|
return endpoint("POST", client.endpoint(`/runs/${encodeURIComponent(runId)}/stop`),
|
|
"hux.cancel_receipt.v1", ["conversation_id"]);
|
|
},
|
|
release: Object.freeze({schema: "hux.release.v1" as const,
|
|
requiredState: "live_verified" as const,
|
|
featureFlags: Object.freeze([FOUNDATION_FLAG, ACTIVITY_TIMELINE_FLAG, AUTONOMY_FLAG])}),
|
|
});
|
|
}
|