- 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
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:
- API routes registered by that module return
404 Not Foundwith body{"error": "module '<id>' is not enabled"}. - Web UI hides the module's navigation entry, page, and any inline UI elements (e.g., project tags on item cards).
- SSE events from the module are not broadcast.
- Background goroutines (e.g., job timeout sweeper, runner heartbeat checker) are not started.
- 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:
- Parse
config.yamlinto Go config struct. - Query
settings_overrides— merge each key into the struct using dotted path resolution. - Apply environment variable overrides (existing
SILO_*vars). - Query
module_state— override default enabled/disabled from YAML. - Validate module dependencies.
- Register only enabled modules' route groups.
- Start only enabled modules' background goroutines.
5.4 Runtime Updates
When an admin saves settings via PUT /api/admin/settings/{module}:
- Validate the payload against the module's config schema.
- Write changed keys to
settings_overrides. - Update
module_stateifenabledchanged. - Apply changes to the in-memory config (hot reload where safe).
- Broadcast
settings.changedSSE event with{module, enabled, changed_keys}. - For changes that require restart (e.g.,
server.port,database.*), return arestart_required: trueflag 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
- Migration 014 —
settings_overridesandmodule_statetables. - Config loader refactor — YAML → DB merge → env override pipeline.
- Module registry — Go struct defining all modules with metadata, dependencies, defaults.
GET /api/modules— public endpoint, no auth.RequireModulemiddleware — gate route groups by module state.- Admin settings API —
GET/PUT /api/admin/settings/{module}, test endpoints. - Web UI settings page — module cards with toggles, config forms, test buttons.
- SSE integration —
settings.changedevent 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
sessionsmodule 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
- CONFIGURATION.md — Current config reference
- ROADMAP.md — Module manifest, API endpoint registry
- AUTH.md — Authentication architecture
- WORKERS.md — Job queue system
- DAG.md — Dependency DAG specification
- SPECIFICATION.md — Full endpoint listing