Skip to content

feat: implement backend CRUD endpoints for company, departments, and agents (all 9 write paths are missing) #1081

@Aureliolo

Description

@Aureliolo

Context

The web dashboard exposes full CRUD affordances for the company configuration — Edit Organization → General / Agents / Departments tabs, the department edit drawer, the agent edit drawer, create dialogs, reorder via drag-drop, and Org Chart drag-drop agent reassignment. All of these live in the Edit Organization and Org Chart pages and feel like "normal" editing from the operator's perspective.

The catch: none of the write paths actually work. The backend never implemented the mutation endpoints. The frontend web/src/api/endpoints/company.ts has a comment (line 41) that has been there since this area of the dashboard was built:

// ── Mutation stubs (backend endpoints not yet implemented) ───

And indeed, querying the backend's /api/v1 for CompanyController, DepartmentController, and AgentController confirms it — every controller is read-only except for the ceremony-policy sub-resource on DepartmentController. Concretely, hitting any of the following returns 405 Method Not Allowed:

Frontend call Backend status
PATCH /company (updateCompany) missing
POST /departments (createDepartment) missing
PATCH /departments/{name} (updateDepartment) missing
DELETE /departments/{name} (deleteDepartment) missing
POST /company/reorder-departments (reorderDepartments) missing
POST /agents (createAgentOrg) missing
PATCH /agents/{name} (updateAgentOrg) missing
DELETE /agents/{name} (deleteAgent) missing
POST /departments/{name}/reorder-agents (reorderAgents) missing

This was discovered during local operator testing of PR #1074 when editing a department and clicking Save produced a 405. The UI offered no indication that editing is not yet available, so the operator assumed their install was broken.

The existing writable surfaces in this area are only PUT /departments/{name}/ceremony-policy and DELETE /departments/{name}/ceremony-policy (the ceremony policy is a sub-resource with its own CRUD). Everything above those is read-only.

Proposed fix

Backend (src/synthorg/api/controllers/)

Add handlers for all 9 missing mutations. The persistence story is already in place: ConfigResolver.get_departments() / get_agents() read from SettingsService under the company namespace with keys departments and agents, and SettingsService.set() can write new values back. The write handlers should:

  1. Read the current list via resolver.get_departments() / resolver.get_agents().
  2. Apply the mutation (add / update / remove / reorder) with full Pydantic validation (not ad-hoc dict merges).
  3. Write the updated list back via settings_service.set(namespace="company", key="departments"|"agents", value=[...]).
  4. Broadcast a WebSocket event on the agents / departments channel so other connected dashboards update in-place (consistent with the existing read-side WS plumbing in useOrgChartData).
  5. Return the updated resource envelope in the project's standard ApiResponse[T] shape.

Endpoints to add:

  • PATCH /api/v1/company -- update top-level company metadata (name, communication pattern, monthly budget, currency, etc.)
  • POST /api/v1/departments -- create a new department (must not duplicate an existing name)
  • PATCH /api/v1/departments/{name} -- update an existing department (display name, budget percent, description, teams list -- the teams list is also what feat: in-dashboard team editing -- create, rename, reassign, delete teams within a department #1079 wants to unblock)
  • DELETE /api/v1/departments/{name} -- delete a department; must reject (or reassign) if agents are still attached, and must refuse to delete the executive department while the CEO lives there
  • POST /api/v1/company/reorder-departments -- accept an ordered_names: list[str] and rewrite the departments tuple in that order
  • POST /api/v1/agents -- create a new agent in a specified department
  • PATCH /api/v1/agents/{name} -- update an agent (role, level, department -- the Org Chart drag-drop uses this to move an agent between departments)
  • DELETE /api/v1/agents/{name} -- delete an agent
  • POST /api/v1/departments/{name}/reorder-agents -- accept an ordered_names: list[str] and rewrite the agent order scoped to that department

Cross-cutting concerns

  • Guards / auth: all mutations must go through require_write_access (or a narrower require_ceo_or_manager for structural changes), mirroring the ceremony-policy handlers that already exist on DepartmentController.
  • Validation: reuse the Pydantic models from core.company (Department, AgentConfig) for full schema validation at the boundary. Do not hand-roll field checks in the handler.
  • Concurrent edits: add ETag / If-Match optimistic concurrency (project already has the infrastructure -- see api/etag.py), so two operators editing the same department don't silently clobber each other.
  • Referential integrity:
    • Deleting a department with agents attached -> 409 Conflict with a helpful message ("Reassign or remove the N agents first").
    • Deleting an agent that is the current CEO -> 409 Conflict unless the caller explicitly opts in to CEO reassignment.
    • Creating / updating with a department name that does not exist -> 422.
    • Reorder endpoints must validate that the payload is a complete permutation of the existing names (no additions, no removals) and 422 otherwise.
  • Budget total invariant: after any department mutation, the sum of budget_percent may deviate from 100. Emit a warning log (matching feat: rebalance department budgets on pack application + validate total !=100% #1080's approach) but do not block the write -- feat: rebalance department budgets on pack application + validate total !=100% #1080 tracks the full rebalance flow.
  • WebSocket broadcast: publish agents.* / departments.* events so useCompanyStore.updateFromWsEvent can reconcile on other open tabs. The event schema should mirror what the read side already consumes.
  • Event logging: new events under synthorg.observability.events.company (or reuse an existing namespace) -- DEPARTMENT_CREATED, DEPARTMENT_UPDATED, DEPARTMENT_DELETED, DEPARTMENT_REORDERED, AGENT_CREATED, AGENT_UPDATED, AGENT_DELETED, AGENTS_REORDERED, COMPANY_UPDATED.

Frontend (web/src/api/endpoints/company.ts)

  • Remove the "Mutation stubs" comment at line 41 once the backend lands.
  • No signature changes required -- the stubs already return the right shapes; they just need to actually reach a live backend.
  • Remove any short-term "Coming soon" UI gates (see "Short-term UX" below) once the real endpoints land.
  • Extend the WS reducer in useCompanyStore and useAgentsStore to handle the new event types emitted by the backend.

Tests

  • Unit tests per handler covering: happy path, 404 on missing resource, 409 on referential integrity failures, 422 on invalid payload, ETag mismatch, auth failure.
  • Integration tests that round-trip config through SettingsService (read -> mutate -> read again).
  • Frontend Vitest tests for the store reducers consuming the new WS events.

Short-term UX (already landed in PR #1074)

While the backend work is scheduled, the dashboard hides or disables every write path and surfaces a single banner pointing at this issue so operators do not hit silent 405s:

  • Edit Organization page: top-of-page info banner linking to this issue.
  • DepartmentEditDrawer: Save / Delete buttons disabled with tooltip.
  • DepartmentCreateDialog, AgentCreateDialog: disabled trigger buttons with tooltip.
  • GeneralTab: Save button disabled with tooltip.
  • DepartmentsTab + AgentsTab: drag-drop reorder disabled.
  • OrgChartPage: drag-drop agent reassignment disabled (nodesDraggable={false} in hierarchy view).

Once this issue is implemented, the UX gates should be removed in the same PR so the experience snaps back to normal.

Out of scope

Acceptance criteria

  • All 9 endpoints land on the backend with full Pydantic validation and project-standard ApiResponse shape.
  • Every mutation emits a domain event under synthorg.observability.events.* and a WebSocket broadcast on the corresponding channel.
  • Referential integrity: deleting a department with attached agents returns 409, not a dangling write; deleting a CEO agent without opt-in returns 409.
  • Optimistic concurrency: repeated PATCH with a stale ETag returns 412 Precondition Failed.
  • Backend unit + integration test coverage for happy path, 404, 409, 422, 412, auth failure on every handler.
  • Frontend removes the "Mutation stubs" comment in web/src/api/endpoints/company.ts and the short-term "Coming soon" UI gates.
  • Frontend WS reducers handle the new event types so multi-tab edits stay in sync.
  • useCompanyStore.optimisticReassignAgent (already present, currently hits a dead endpoint) actually persists the new assignment.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions