import { AXIS_COMMANDS, axisGate } from "./axis-contract.mjs"; export const COMMAND_ACTIONS = Object.freeze(Object.fromEntries(Object.entries(AXIS_COMMANDS).map(([action, command]) => [command, action]))); export class AxisGateError extends Error { constructor(result) { super(result.reason); this.name = "AxisGateError"; this.code = result.reason; this.gate = result; } } export class AxisOperationController { constructor(client) { if (!client || typeof client.send !== "function") throw new Error("axis_operation_client_required"); this.client = client; this.counter = 0; this.trace = []; } nextOperationId(kind) { this.counter += 1; return `axis-${String(kind).toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${String(this.counter).padStart(6, "0")}`; } async execute(kind, payload = {}, { state = {}, action = COMMAND_ACTIONS[kind] ?? null, gate = true } = {}) { const operationId = this.nextOperationId(kind); const gateResult = action ? axisGate(action, state) : { action: null, enabled: true, reason: "axis_unrestricted_internal_command" }; if (gate && !gateResult.enabled) { this.trace.push(Object.freeze({ operation_id: operationId, kind, action, phase: "blocked", reason: gateResult.reason })); throw new AxisGateError(gateResult); } this.trace.push(Object.freeze({ operation_id: operationId, kind, action, phase: "pending", reason: gateResult.reason })); try { const response = await this.client.send(kind, payload, operationId); this.trace.push(Object.freeze({ operation_id: operationId, kind, action, phase: "result", response_kind: response?.kind ?? null })); return Object.freeze({ operationId, response, gate: gateResult }); } catch (error) { this.trace.push(Object.freeze({ operation_id: operationId, kind, action, phase: "error", reason: error?.message ?? String(error) })); throw error; } } exportTrace() { return structuredClone(this.trace); } }