Files
AgentCompany/src/main/ipc-budget-handlers.ts
T
ZacharyZhang-NY feea5c1fe9 feat(05-02): IPC handlers, preload bridge, and ServiceGraph wiring for budget enforcement
- Create ipc-budget-handlers.ts with 6 budget channel handlers (save/delete/list policies, list/resolve incidents, get status)
- Add budgetService to RegisterDomainHandlersDependencies interface
- Register budget handlers in ipc-domain-handlers.ts composition
- Add BudgetEnforcementService lazy slot in service-graph.ts with getOrCreateBudgetEnforcementService
- Pass budgetService to domain handler registration in main-runtime-support.ts
- Fix preload bridge to pass {companyId} object for listBudgetPolicies and getBudgetStatus
- D-06: checkAutoResolve called on saveBudgetPolicy to auto-resolve incidents on limit increase
2026-03-26 20:57:52 -04:00

100 lines
3.6 KiB
TypeScript

import {
IPC_CHANNELS,
budgetPolicyInputSchema,
listBudgetPoliciesSchema,
deleteBudgetPolicySchema,
listBudgetIncidentsSchema,
resolveBudgetIncidentSchema,
getBudgetStatusSchema,
} from "@shared/contracts";
import { fail, ok, type RegisterDomainHandlersDependencies } from "./ipc-domain-common";
import type { createBudgetEnforcementService } from "./budget-enforcement-service";
import type { AppDatabase } from "./database";
interface BudgetHandlerDependencies extends Pick<
RegisterDomainHandlersDependencies,
"registerHandle" | "ensureCompanyExists" | "publishDomainChanged"
> {
budgetService: ReturnType<typeof createBudgetEnforcementService> | null;
db: AppDatabase;
}
export function registerBudgetHandlers({
registerHandle,
ensureCompanyExists,
publishDomainChanged,
budgetService,
db,
}: BudgetHandlerDependencies) {
function requireBudgetService() {
if (!budgetService) {
return fail("SERVICE_NOT_READY", "Budget enforcement service is not initialized.");
}
return null;
}
// ---- Save (create/update) a budget policy ----
registerHandle(IPC_CHANNELS.saveBudgetPolicy, async (_event, payload) => {
const parsed = budgetPolicyInputSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const id = db.saveBudgetPolicy(parsed);
// D-06: Auto-resolve incidents if limit was increased above current spend
if (budgetService) {
try {
budgetService.checkAutoResolve(parsed.companyId, id);
} catch {
// Best effort -- service may not be ready
}
}
publishDomainChanged();
return ok(id);
});
// ---- Delete a budget policy ----
registerHandle(IPC_CHANNELS.deleteBudgetPolicy, async (_event, payload) => {
const parsed = deleteBudgetPolicySchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
db.deleteBudgetPolicy(parsed.companyId, parsed.policyId);
publishDomainChanged();
return ok(true);
});
// ---- List all budget policies for a company ----
registerHandle(IPC_CHANNELS.listBudgetPolicies, async (_event, payload) => {
const parsed = listBudgetPoliciesSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
return ok(db.listBudgetPolicies(parsed.companyId));
});
// ---- List budget incidents ----
registerHandle(IPC_CHANNELS.listBudgetIncidents, async (_event, payload) => {
const parsed = listBudgetIncidentsSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
return ok(db.listBudgetIncidents(parsed.companyId, parsed.status));
});
// ---- Resolve a budget incident manually ----
registerHandle(IPC_CHANNELS.resolveBudgetIncident, async (_event, payload) => {
const parsed = resolveBudgetIncidentSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
db.resolveBudgetIncident(parsed.incidentId, parsed.companyId);
publishDomainChanged();
return ok(true);
});
// ---- Get budget status summary ----
registerHandle(IPC_CHANNELS.getBudgetStatus, async (_event, payload) => {
const parsed = getBudgetStatusSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const serviceError = requireBudgetService();
if (serviceError) return serviceError;
return ok(budgetService!.getBudgetStatus(parsed.companyId));
});
}