Files
silo/docs/MODULES.md
Forbes 21c592bcb2 docs: update all docs for sessions, solver, approvals, and recent features
- STATUS.md: migration count 18→23, endpoint count 86→~140, add approval
  workflows, solver service, workstations, edit sessions, SSE targeted
  delivery rows, update test file count 9→31, add migrations 019-023
- MODULES.md: add solver and sessions to registry, dependencies, endpoint
  mappings (sections 3.11, 3.12), discovery response, admin settings,
  config YAML, and future considerations
- CONFIGURATION.md: add Approval Workflows, Solver, and Modules config
  sections, add SILO_SOLVER_DEFAULT env var
- ROADMAP.md: mark Job Queue Complete (Tier 0), Audit Trail Complete
  (Tier 1), Approval/ECO Complete (Tier 4), update Workflow Engine tasks,
  add Recently Completed section, update counts, resolve job queue question
- GAP_ANALYSIS.md: mark approval workflow Implemented, locking Partial,
  update workflow comparison (C.2), update check-in/check-out to Partial,
  task scheduler to Full, update endpoint counts, rewrite Appendix A
- INSTALL.md: add MODULES.md, WORKERS.md, SOLVER.md to Further Reading
- WORKERS.md: status Draft→Implemented
- SOLVER.md: add spec doc, mark Phase 3b as complete
2026-03-03 13:26:08 -06:00

23 KiB

Module System Specification

Status: Draft Last Updated: 2026-03-01


1. Purpose

Silo's module system defines the boundary between required infrastructure and optional capabilities. Each module groups a set of API endpoints, UI views, and configuration parameters. Modules can be enabled or disabled at runtime by administrators via the web UI, and clients can query which modules are active to adapt their feature set.

The goal: after initial deployment (where config.yaml sets database, storage, and server bind), all further operational configuration happens through the admin settings UI. The YAML file becomes the bootstrap; the database becomes the runtime source of truth.


2. Module Registry

2.1 Required Modules

These cannot be disabled. They define what Silo is.

Module ID Name Description
core Core PDM Items, revisions, files, BOM, search, import/export, part number generation
schemas Schemas Part numbering schema parsing, segment management, form descriptors
storage Storage Filesystem storage

2.2 Optional Modules

Module ID Name Default Description
auth Authentication true Local, LDAP, OIDC authentication and RBAC
projects Projects true Project management and item tagging
audit Audit true Audit logging, completeness scoring
odoo Odoo ERP false Odoo integration (config, sync-log, push/pull)
freecad Create Integration true URI scheme, executable path, client settings
jobs Job Queue false Async compute jobs, runner management
dag Dependency DAG false Feature DAG sync, validation states, interference detection
solver Solver false Assembly constraint solving via server-side runners
sessions Sessions true Workstation registration, edit sessions, and presence tracking

2.3 Module Dependencies

Some modules require others to function:

Module Requires
dag jobs
jobs auth (runner tokens)
odoo auth
solver jobs
sessions auth

When enabling a module, its dependencies are validated. The server rejects enabling dag without jobs. Disabling a module that others depend on shows a warning listing dependents.


3. Endpoint-to-Module Mapping

3.1 core (required)

# Health
GET    /health
GET    /ready

# Items
GET    /api/items
GET    /api/items/search
GET    /api/items/by-uuid/{uuid}
GET    /api/items/export.csv
GET    /api/items/template.csv
GET    /api/items/export.ods
GET    /api/items/template.ods
POST   /api/items
POST   /api/items/import
POST   /api/items/import.ods
GET    /api/items/{partNumber}
PUT    /api/items/{partNumber}
DELETE /api/items/{partNumber}

# Revisions
GET    /api/items/{partNumber}/revisions
GET    /api/items/{partNumber}/revisions/compare
GET    /api/items/{partNumber}/revisions/{revision}
POST   /api/items/{partNumber}/revisions
PATCH  /api/items/{partNumber}/revisions/{revision}
POST   /api/items/{partNumber}/revisions/{revision}/rollback

# Files
GET    /api/items/{partNumber}/files
GET    /api/items/{partNumber}/file
GET    /api/items/{partNumber}/file/{revision}
POST   /api/items/{partNumber}/file
POST   /api/items/{partNumber}/files
DELETE /api/items/{partNumber}/files/{fileId}
PUT    /api/items/{partNumber}/thumbnail
POST   /api/uploads/presign

# BOM
GET    /api/items/{partNumber}/bom
GET    /api/items/{partNumber}/bom/expanded
GET    /api/items/{partNumber}/bom/flat
GET    /api/items/{partNumber}/bom/cost
GET    /api/items/{partNumber}/bom/where-used
GET    /api/items/{partNumber}/bom/export.csv
GET    /api/items/{partNumber}/bom/export.ods
POST   /api/items/{partNumber}/bom
POST   /api/items/{partNumber}/bom/import
POST   /api/items/{partNumber}/bom/merge
PUT    /api/items/{partNumber}/bom/{childPartNumber}
DELETE /api/items/{partNumber}/bom/{childPartNumber}

# .kc Metadata
GET    /api/items/{partNumber}/metadata
PUT    /api/items/{partNumber}/metadata
PATCH  /api/items/{partNumber}/metadata/lifecycle
PATCH  /api/items/{partNumber}/metadata/tags

# .kc Dependencies
GET    /api/items/{partNumber}/dependencies
GET    /api/items/{partNumber}/dependencies/resolve

# .kc Macros
GET    /api/items/{partNumber}/macros
GET    /api/items/{partNumber}/macros/{filename}

# Part Number Generation
POST   /api/generate-part-number

# Sheets
POST   /api/sheets/diff

# Settings & Modules (admin)
GET    /api/modules
GET    /api/admin/settings
GET    /api/admin/settings/{module}
PUT    /api/admin/settings/{module}
POST   /api/admin/settings/{module}/test

3.2 schemas (required)

GET    /api/schemas
GET    /api/schemas/{name}
GET    /api/schemas/{name}/form
POST   /api/schemas/{name}/segments/{segment}/values
PUT    /api/schemas/{name}/segments/{segment}/values/{code}
DELETE /api/schemas/{name}/segments/{segment}/values/{code}

3.3 storage (required)

No dedicated endpoints — storage is consumed internally by file upload/download in core. Exposed through admin settings for connection status visibility.

3.4 auth

# Public (login flow)
GET    /login
POST   /login
POST   /logout
GET    /auth/oidc
GET    /auth/callback

# Authenticated
GET    /api/auth/me
GET    /api/auth/tokens
POST   /api/auth/tokens
DELETE /api/auth/tokens/{id}

# Web UI
GET    /settings                          (account info, tokens)
POST   /settings/tokens
POST   /settings/tokens/{id}/revoke

When auth is disabled, all routes are open and a synthetic dev admin user is injected (current behavior).

3.5 projects

GET    /api/projects
GET    /api/projects/{code}
GET    /api/projects/{code}/items
GET    /api/projects/{code}/sheet.ods
POST   /api/projects
PUT    /api/projects/{code}
DELETE /api/projects/{code}

# Item-project tagging
GET    /api/items/{partNumber}/projects
POST   /api/items/{partNumber}/projects
DELETE /api/items/{partNumber}/projects/{code}

When disabled: project tag endpoints return 404, project columns are hidden in UI list views, project filter is removed from item search.

3.6 audit

GET    /api/audit/completeness
GET    /api/audit/completeness/{partNumber}

When disabled: audit log table continues to receive writes (it's part of core middleware), but the completeness scoring endpoints and the Audit page in the web UI are hidden. Future: retention policies, export, and compliance reporting endpoints live here.

3.7 odoo

GET    /api/integrations/odoo/config
GET    /api/integrations/odoo/sync-log
PUT    /api/integrations/odoo/config
POST   /api/integrations/odoo/test-connection
POST   /api/integrations/odoo/sync/push/{partNumber}
POST   /api/integrations/odoo/sync/pull/{odooId}

3.8 freecad

No dedicated API endpoints currently. Configures URI scheme and executable path used by the web UI's "Open in Create" links and by CLI operations. Future: client configuration distribution endpoint.

3.9 jobs

# User-facing
GET    /api/jobs
GET    /api/jobs/{jobID}
GET    /api/jobs/{jobID}/logs
POST   /api/jobs
POST   /api/jobs/{jobID}/cancel

# Job definitions
GET    /api/job-definitions
GET    /api/job-definitions/{name}
POST   /api/job-definitions/reload

# Runner management (admin)
GET    /api/runners
POST   /api/runners
DELETE /api/runners/{runnerID}

# Runner-facing (runner token auth)
POST   /api/runner/heartbeat
POST   /api/runner/claim
PUT    /api/runner/jobs/{jobID}/progress
POST   /api/runner/jobs/{jobID}/complete
POST   /api/runner/jobs/{jobID}/fail
POST   /api/runner/jobs/{jobID}/log
PUT    /api/runner/jobs/{jobID}/dag

3.10 dag

GET    /api/items/{partNumber}/dag
GET    /api/items/{partNumber}/dag/forward-cone/{nodeKey}
GET    /api/items/{partNumber}/dag/dirty
PUT    /api/items/{partNumber}/dag
POST   /api/items/{partNumber}/dag/mark-dirty/{nodeKey}

3.11 solver

GET    /api/solver/jobs
GET    /api/solver/jobs/{jobID}
POST   /api/solver/jobs
POST   /api/solver/jobs/{jobID}/cancel
GET    /api/solver/solvers
GET    /api/solver/results/{partNumber}

3.12 sessions

# Workstation management
GET    /api/workstations
POST   /api/workstations
DELETE /api/workstations/{workstationID}

# Edit sessions (user-scoped)
GET    /api/edit-sessions

# Edit sessions (item-scoped)
GET    /api/items/{partNumber}/edit-sessions
POST   /api/items/{partNumber}/edit-sessions
DELETE /api/items/{partNumber}/edit-sessions/{sessionID}

4. Disabled Module Behavior

When a module is disabled:

  1. API routes registered by that module return 404 Not Found with body {"error": "module '<id>' is not enabled"}.
  2. Web UI hides the module's navigation entry, page, and any inline UI elements (e.g., project tags on item cards).
  3. SSE events from the module are not broadcast.
  4. Background goroutines (e.g., job timeout sweeper, runner heartbeat checker) are not started.
  5. Database tables are not dropped — they remain for re-enablement. No data loss on disable/enable cycle.

Implementation: each module's route group is wrapped in a middleware check:

func RequireModule(id string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if !modules.IsEnabled(id) {
                http.Error(w, `{"error":"module '`+id+`' is not enabled"}`, 404)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

5. Configuration Persistence

5.1 Precedence

Environment variables  (highest — always wins, secrets live here)
        ↓
Database overrides     (admin UI writes here)
        ↓
config.yaml            (lowest — bootstrap defaults)

5.2 Database Table

-- Migration 014_settings.sql
CREATE TABLE settings_overrides (
    key         TEXT PRIMARY KEY,          -- dotted path: "auth.ldap.enabled"
    value       JSONB NOT NULL,           -- typed value
    updated_by  TEXT NOT NULL,            -- username
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE module_state (
    module_id   TEXT PRIMARY KEY,         -- "auth", "projects", etc.
    enabled     BOOLEAN NOT NULL,
    updated_by  TEXT NOT NULL,
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

5.3 Load Sequence

On startup:

  1. Parse config.yaml into Go config struct.
  2. Query settings_overrides — merge each key into the struct using dotted path resolution.
  3. Apply environment variable overrides (existing SILO_* vars).
  4. Query module_state — override default enabled/disabled from YAML.
  5. Validate module dependencies.
  6. Register only enabled modules' route groups.
  7. Start only enabled modules' background goroutines.

5.4 Runtime Updates

When an admin saves settings via PUT /api/admin/settings/{module}:

  1. Validate the payload against the module's config schema.
  2. Write changed keys to settings_overrides.
  3. Update module_state if enabled changed.
  4. Apply changes to the in-memory config (hot reload where safe).
  5. Broadcast settings.changed SSE event with {module, enabled, changed_keys}.
  6. For changes that require restart (e.g., server.port, database.*), return a restart_required: true flag in the response. The UI shows a banner.

5.5 What Requires Restart

Config Area Hot Reload Restart Required
Module enable/disable Yes No
auth.* provider toggles Yes No
auth.cors.allowed_origins Yes No
odoo.* connection settings Yes No
freecad.* Yes No
jobs.* timeouts, directory Yes No
server.host, server.port No Yes
database.* No Yes
storage.* No Yes
schemas.directory No Yes

6. Public Module Discovery Endpoint

GET /api/modules

No authentication required. Clients need this pre-login to know whether OIDC is available, whether projects exist, etc.

6.1 Response

{
  "modules": {
    "core": {
      "enabled": true,
      "required": true,
      "name": "Core PDM",
      "version": "0.2"
    },
    "schemas": {
      "enabled": true,
      "required": true,
      "name": "Schemas"
    },
    "storage": {
      "enabled": true,
      "required": true,
      "name": "Storage"
    },
    "auth": {
      "enabled": true,
      "required": false,
      "name": "Authentication",
      "config": {
        "local_enabled": true,
        "ldap_enabled": true,
        "oidc_enabled": true,
        "oidc_issuer_url": "https://keycloak.example.com/realms/silo"
      }
    },
    "projects": {
      "enabled": true,
      "required": false,
      "name": "Projects"
    },
    "audit": {
      "enabled": true,
      "required": false,
      "name": "Audit"
    },
    "odoo": {
      "enabled": false,
      "required": false,
      "name": "Odoo ERP"
    },
    "freecad": {
      "enabled": true,
      "required": false,
      "name": "Create Integration",
      "config": {
        "uri_scheme": "silo"
      }
    },
    "jobs": {
      "enabled": false,
      "required": false,
      "name": "Job Queue"
    },
    "dag": {
      "enabled": false,
      "required": false,
      "name": "Dependency DAG",
      "depends_on": ["jobs"]
    },
    "solver": {
      "enabled": false,
      "required": false,
      "name": "Solver",
      "depends_on": ["jobs"]
    },
    "sessions": {
      "enabled": true,
      "required": false,
      "name": "Sessions",
      "depends_on": ["auth"]
    }
  },
  "server": {
    "version": "0.2",
    "read_only": false
  }
}

The config sub-object exposes only public, non-secret metadata needed by clients. Never includes passwords, tokens, or secret keys.


7. Admin Settings Endpoints

7.1 Get All Settings

GET /api/admin/settings
Authorization: Bearer <admin token>

Returns full config grouped by module with secrets redacted:

{
  "core": {
    "server": {
      "host": "0.0.0.0",
      "port": 8080,
      "base_url": "https://silo.example.com",
      "read_only": false
    }
  },
  "schemas": {
    "directory": "/etc/silo/schemas",
    "default": "kindred-rd"
  },
  "storage": {
    "backend": "filesystem",
    "filesystem": {
      "root_dir": "/var/lib/silo/data"
    },
    "status": "connected"
  },
  "database": {
    "host": "postgres",
    "port": 5432,
    "name": "silo",
    "user": "silo",
    "password": "****",
    "sslmode": "disable",
    "max_connections": 10,
    "status": "connected"
  },
  "auth": {
    "enabled": true,
    "session_secret": "****",
    "local": { "enabled": true },
    "ldap": {
      "enabled": true,
      "url": "ldaps://ipa.example.com",
      "base_dn": "dc=kindred,dc=internal",
      "user_search_dn": "cn=users,cn=accounts,dc=kindred,dc=internal",
      "bind_password": "****",
      "role_mapping": { "...": "..." }
    },
    "oidc": {
      "enabled": true,
      "issuer_url": "https://keycloak.example.com/realms/silo",
      "client_id": "silo",
      "client_secret": "****",
      "redirect_url": "https://silo.example.com/auth/callback"
    },
    "cors": { "allowed_origins": ["https://silo.example.com"] }
  },
  "projects": { "enabled": true },
  "audit": { "enabled": true },
  "odoo": { "enabled": false, "url": "", "database": "", "username": "" },
  "freecad": { "uri_scheme": "silo", "executable": "" },
  "jobs": {
    "enabled": false,
    "directory": "/etc/silo/jobdefs",
    "runner_timeout": 90,
    "job_timeout_check": 30,
    "default_priority": 100
  },
  "dag": { "enabled": false },
  "solver": { "enabled": false, "default_solver": "ondsel" },
  "sessions": { "enabled": true }
}

7.2 Get Module Settings

GET /api/admin/settings/{module}

Returns just the module's config block.

7.3 Update Module Settings

PUT /api/admin/settings/{module}
Content-Type: application/json

{
  "enabled": true,
  "ldap": {
    "enabled": true,
    "url": "ldaps://ipa.example.com"
  }
}

Response:

{
  "updated": ["auth.ldap.enabled", "auth.ldap.url"],
  "restart_required": false
}

7.4 Test Connectivity

POST /api/admin/settings/{module}/test

Available for modules with external connections:

Module Test Action
storage Verify filesystem storage directory is accessible
auth (ldap) Attempt LDAP bind with configured credentials
auth (oidc) Fetch OIDC discovery document from issuer URL
odoo Attempt XML-RPC connection to Odoo

Response:

{
  "success": true,
  "message": "LDAP bind successful",
  "latency_ms": 42
}

8. Config YAML Changes

The existing config.yaml gains a modules section. Existing top-level keys remain for backward compatibility — the module system reads from both locations.

# Existing keys (unchanged, still work)
server:
  host: "0.0.0.0"
  port: 8080

database:
  host: postgres
  port: 5432
  name: silo
  user: silo
  password: silodev
  sslmode: disable

storage:
  backend: filesystem
  filesystem:
    root_dir: /var/lib/silo/data

schemas:
  directory: /etc/silo/schemas

auth:
  enabled: true
  session_secret: change-me
  local:
    enabled: true

# New: explicit module toggles (optional, defaults shown)
modules:
  projects:
    enabled: true
  audit:
    enabled: true
  odoo:
    enabled: false
  freecad:
    enabled: true
    uri_scheme: silo
  jobs:
    enabled: false
    directory: /etc/silo/jobdefs
    runner_timeout: 90
    job_timeout_check: 30
    default_priority: 100
  dag:
    enabled: false
  solver:
    enabled: false
    default_solver: ondsel
  sessions:
    enabled: true

If a module is not listed under modules:, its default enabled state from Section 2.2 applies. The auth.enabled field continues to control the auth module (no duplication under modules:).


9. SSE Events

settings.changed    {module, enabled, changed_keys[], updated_by}

Broadcast on any admin settings change. The web UI listens for this to:

  • Show/hide navigation entries when modules are toggled.
  • Display a "Settings updated by another admin" toast.
  • Show a "Restart required" banner when flagged.

10. Web UI — Admin Settings Page

The Settings page (/settings) is restructured into sections:

10.1 Existing (unchanged)

  • Account — username, display name, email, auth source, role badge.
  • API Tokens — create, list, revoke.

10.2 New: Module Configuration (admin only)

Visible only to admin users. Each module gets a collapsible card:

┌─────────────────────────────────────────────────────┐
│ [toggle] Authentication                    [status] │
├─────────────────────────────────────────────────────┤
│                                                     │
│  ── Local Auth ──────────────────────────────────── │
│  Enabled: [toggle]                                  │
│                                                     │
│  ── LDAP / FreeIPA ──────────────────────────────── │
│  Enabled: [toggle]                                  │
│  URL:     [ldaps://ipa.example.com        ]         │
│  Base DN: [dc=kindred,dc=internal         ]  [Test] │
│                                                     │
│  ── OIDC / Keycloak ────────────────────────────── │
│  Enabled:    [toggle]                               │
│  Issuer URL: [https://keycloak.example.com] [Test]  │
│  Client ID:  [silo                        ]         │
│                                                     │
│  ── CORS ────────────────────────────────────────── │
│  Allowed Origins: [tag input]                       │
│                                                     │
│                                          [Save]     │
└─────────────────────────────────────────────────────┘

Module cards for required modules (core, schemas, storage) show their status and config but have no enable/disable toggle.

Status indicators per module:

Status Badge Meaning
Active green Enabled and operational
Disabled overlay1 Toggled off
Error red Enabled but connectivity or config issue
Setup Required yellow Enabled but missing required config (e.g., LDAP URL empty)

10.3 Infrastructure Section (admin, read-only)

Shows connection status for required infrastructure:

  • Database — host, port, name, connection pool usage, status badge.
  • Storage — endpoint, bucket, SSL, status badge.

These are read-only in the UI (setup-only via YAML/env). The "Test" button is available to verify connectivity.


11. Implementation Order

  1. Migration 014settings_overrides and module_state tables.
  2. Config loader refactor — YAML → DB merge → env override pipeline.
  3. Module registry — Go struct defining all modules with metadata, dependencies, defaults.
  4. GET /api/modules — public endpoint, no auth.
  5. RequireModule middleware — gate route groups by module state.
  6. Admin settings APIGET/PUT /api/admin/settings/{module}, test endpoints.
  7. Web UI settings page — module cards with toggles, config forms, test buttons.
  8. SSE integrationsettings.changed event broadcast.

12. Future Considerations

  • Module manifest format — per ROADMAP.md, each module will eventually declare routes, views, hooks, and permissions via a manifest. This spec covers the runtime module registry; the manifest format is TBD.
  • Custom modules — third-party modules that register against the endpoint registry. Requires the manifest contract and a plugin loading mechanism.
  • Per-module permissions — beyond the current role hierarchy, modules may define fine-grained scopes (e.g., jobs:admin, dag:write).
  • Location & Inventory module — when the Location/Inventory API is implemented (tables already exist), it becomes a new optional module.
  • Notifications module — per ROADMAP.md Tier 1, notifications/subscriptions will be a dedicated module.
  • Soft interference detection — the sessions module currently enforces hard interference (unique index on item + context_level + object_id). Soft interference detection (overlapping dependency cones) is planned as a follow-up.

13. References