How OpenWork Den Manages Organization Policies: Architecture and Implementation

TLDR: OpenWork Den manages organization policies through a centralized desktop‑policy system that stores JSON‑based rules in PostgreSQL, exposes REST endpoints for CRUD operations, and enforces permissions via WebSocket‑driven updates to the desktop client.

OpenWork Den acts as the control plane for enterprise OpenWork installations, governing feature access through granular organization policies defined in the different-ai/openwork repository. This technical guide examines how OpenWork Den manages organization policies, from TypeScript type definitions in packages/types to real‑time enforcement in the desktop application.

Policy Schema and Type Definitions

The foundation of the policy system lives in packages/types/src/den/desktop-policies.ts. This file exports TypeScript interfaces that define the structure of an organization’s desktop policy, including boolean flags such as allowModelAccess and allowToolX, along with metadata fields like policyName, isEnabled, and assignments.

// packages/types/src/den/desktop-policies.ts
export interface DesktopPolicy {
  id: string;
  orgId: string;
  policyName: string;
  isEnabled: boolean;
  isDefault: boolean;
  assignments?: string[]; // User or team IDs
  allowModelAccess: boolean;
  allowedTools: string[];
  // Additional feature flags...
}

The type system ensures that every policy object consumed by the API or desktop client adheres to a strict contract, preventing runtime errors caused by missing fields.

Database Storage and Default Policies

Policies persist in the Den server’s PostgreSQL database within the desktop_policies table. A database migration located at packages/den-api/migrations/2023...-desktop-policies.sql creates this table with a JSON blob column for the policy payload alongside relational metadata (orgId, isDefault, createdAt).

Every organization must maintain exactly one default policy where isDefault: true. This policy serves as the fallback for any member whose profile lacks an explicit policy assignment, ensuring consistent security enforcement during onboarding or migrations.

REST API Implementation

Route Endpoints

The Den server exposes a full CRUD interface through packages/den-api/src/routes/desktop-policies.ts. These endpoints handle policy management for administrators and read‑only access for members:

// packages/den-api/src/routes/desktop-policies.ts
router.get('/v1/desktop-policies', listPolicies);
router.get('/v1/desktop-policies/:id', getPolicy);
router.post('/v1/desktop-policies', createPolicy);
router.patch('/v1/desktop-policies/:id', updatePolicy);
router.delete('/v1/desktop-policies/:id', deletePolicy);
  • GET requests filter results by the caller’s organization.
  • POST and PATCH requests validate that the caller is an organization admin.
  • DELETE operations prevent removal of the default policy to avoid orphaned users.

Controller Logic and Validation

The controller at packages/den-api/src/controllers/desktop-policies.ts implements the business logic behind these routes. It performs authorization checks to restrict mutating operations to admins while allowing members to read their assigned policies.

Incoming payloads undergo normalization via the normalizeDesktopPolicyValue helper function. This utility guarantees that every boolean flag has a deterministic true or false value, protecting downstream components from schema drift when older policy objects lack newer feature flags.

// packages/den-api/src/controllers/desktop-policies.ts
export function normalizeDesktopPolicyValue(policy: Partial<DesktopPolicy>): DesktopPolicy {
  return {
    ...policy,
    allowModelAccess: policy.allowModelAccess ?? false,
    allowedTools: policy.allowedTools ?? [],
    isEnabled: policy.isEnabled ?? true,
  } as DesktopPolicy;
}

Client-Side Enforcement

Policy Retrieval and Caching

When the desktop application initializes, it contacts Den to fetch the organization’s default policy from /v1/desktop-policies/:id. The module packages/desktop/src/policy/desktopPolicy.ts manages this lifecycle:

  • Fetches the policy JSON from the Den server.
  • Normalizes the payload using the same normalizeDesktopPolicyValue function defined in the shared types package.
  • Caches the result in local storage to eliminate network round‑trips during UI rendering.
  • Exposes helper functions like isModelAllowed(modelId: string): boolean and isToolEnabled(toolName: string): boolean for synchronous access.
// packages/desktop/src/policy/desktopPolicy.ts
export async function loadPolicy(orgId: string): Promise<DesktopPolicy> {
  const response = await fetch(`/v1/desktop-policies/${orgId}`);
  const raw = await response.json();
  const policy = normalizeDesktopPolicyValue(raw);
  localStorage.setItem('desktop_policy', JSON.stringify(policy));
  return policy;
}

UI Feature Gating

UI components import the policy cache to conditionally render features. The model selector component checks policy.allowModelAccess before displaying available LLM options, while the toolbar component iterates over policy.allowedTools to determine which buttons to enable. This client‑side gating ensures that the organization’s security stance is enforced at the presentation layer, independent of server‑side validation.

Real-Time Updates and Synchronization

Den maintains open WebSocket connections with desktop clients to push policy changes immediately. When an administrator modifies a policy via the PATCH endpoint, the server emits a policy:update event through packages/den-api/src/websocket/policy-updates.ts.

Clients receiving this event invalidate their local cache and re‑fetch the latest policy, causing the UI to re‑render without requiring an application restart. If a user’s profile lacks an explicit policy assignment, the client automatically falls back to the organization’s default policy, ensuring uninterrupted access control.

Summary

  • Type definitions in packages/types/src/den/desktop-policies.ts provide the strict schema for policy objects.
  • PostgreSQL stores policies in the desktop_policies table, with every organization required to maintain one default policy.
  • REST endpoints at /v1/desktop-policies support full CRUD operations with admin‑only write restrictions.
  • Normalization via normalizeDesktopPolicyValue ensures backward compatibility as the policy schema evolves.
  • Desktop clients cache policies locally and expose helper functions for UI feature gating.
  • WebSocket events deliver real‑time updates to connected clients when policies change.

Frequently Asked Questions

How does OpenWork Den handle users without an assigned policy?

When a user profile lacks an explicit policy reference, the desktop client automatically falls back to the organization’s default policy (where isDefault: true). This guarantees that new members or users undergoing migrations still operate under the organization’s security baseline without manual intervention.

Can regular members modify organization policies?

No. The controller logic in packages/den-api/src/controllers/desktop-policies.ts enforces role‑based access control that restricts POST, PATCH, and DELETE operations to organization administrators. Regular members can only read policies through the GET endpoints, ensuring that feature access remains centrally governed.

What triggers a policy update on desktop clients?

Den pushes real‑time notifications via WebSocket connections defined in packages/den-api/src/websocket/policy-updates.ts. When an administrator updates a policy through the PATCH endpoint, the server emits a policy:update event to all connected clients, which then invalidate their cache and reload the policy to reflect changes immediately.

How does the system prevent schema drift in older policies?

Both the server and client use the normalizeDesktopPolicyValue function (located in the shared types package and imported by packages/den-api/src/controllers/desktop-policies.ts and packages/desktop/src/policy/desktopPolicy.ts). This utility assigns default values to any missing boolean flags or arrays, ensuring that legacy policy objects remain compatible with newer UI components that expect specific fields to exist.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →