From 64890bf12fec740ccfc4a652d23f3ca1f56df314 Mon Sep 17 00:00:00 2001 From: Jeffrey Smith Date: Fri, 27 Mar 2026 15:03:10 +0000 Subject: [PATCH] Feat v0.2.6 admin settings audit (#10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Roadmap reorder: Extension Lifecycle moved to v0.3.x, settings audit milestones renumbered (v0.2.7→v0.2.6, v0.2.8→v0.2.7, v0.2.9→v0.2.8). Added v0.2.9 for builtin extension retirement — 6 chat-centric extensions (csv-table, diff-viewer, js-sandbox, katex, mermaid, regex) are dormant until a chat surface ships; roadmap tracks converting them to regular packages and removing the auto-seed mechanism. Admin settings E2E verified — all 7 sections (default surface, registration, banner, message bar, footer, vault, email) render and save correctly. Packages surface verified (17/17 loaded). Dead code removed: - sectionCategory(): AI/routing/channel cases, default→system - PublicSettings(): system_prompt, retention_ttl, paste_to_file, allow_user_personas - PolicyDefaults: allow_raw_model_access, default_model - Profile handlers: allow_raw_model_access, kb_direct_access lookups - Test seeds: model_roles setting, allow_raw_model_access policy - packages.js: 'chat' from CORE_IDS Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 26 ++++++++++++++++ ROADMAP.md | 27 +++++++++-------- server/database/testhelper.go | 12 ++------ server/handlers/admin.go | 42 +++----------------------- server/handlers/profile_bootstrap.go | 2 -- server/handlers/profile_permissions.go | 2 -- server/models/models.go | 12 +++----- server/pages/loaders.go | 10 +++--- src/js/sw/surfaces/admin/packages.js | 2 +- 9 files changed, 58 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e7657..ce1980d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ All notable changes to Switchboard Core are documented here. +## [Unreleased] — v0.2.6 + +### Changed + +- **Roadmap reorder**: Extension Lifecycle moved from v0.2.6 to v0.3.x + (Workflows series). Settings audit milestones renumbered: v0.2.7→v0.2.6, + v0.2.8→v0.2.7, v0.2.9→v0.2.8. Added v0.2.9 for builtin extension + retirement (chat-centric extensions dormant until chat surface ships). + +### Removed + +- **Dead admin section categories** in `sectionCategory()`: AI (providers, + models, personas, roles, knowledgeBases, memory), routing (health, routing, + capabilities), and channels. Default category changed from `ai` to `system`. +- **Dead PublicSettings fields**: `system_prompt`/`has_admin_prompt`, + `retention_ttl_days`, `paste_to_file_chars`, `allow_user_personas` policy + — all chat-era with no frontend consumers. +- **Dead PolicyDefaults**: `allow_raw_model_access`, `default_model`. +- **Dead policy lookups**: `allow_raw_model_access` and `kb_direct_access` + from profile bootstrap and permissions handlers. +- **Dead test seed data**: `model_roles` global setting, `allow_raw_model_access` + policy from test helper. +- **Dead CORE_IDS entry**: Removed `chat` from packages admin page. + +--- + ## [Unreleased] — v0.2.5 ### Added diff --git a/ROADMAP.md b/ROADMAP.md index 77707d8..adbeb88 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -89,42 +89,45 @@ SDK stabilization, and the first rebuilt extension (tasks). | Default surface routing | ✅ | Resolution chain: user preference → global config → first extension → `/welcome`. Users can set personal default in Settings > General. Admin sets global default in Admin > Settings. | | Admin navigation | ✅ | Replaced Back button with UserMenu in admin topbar. Eliminates back-button infinite loop. | -### v0.2.6 — Extension Lifecycle +### v0.2.6 — Admin Settings Audit | Step | Status | Description | |------|--------|-------------| -| Extension lifecycle | ⬚ | Define permanent vs PoC extensions. Package graduation criteria. Dependency policy. What ships with core vs what's installed separately. | +| Admin settings E2E | ✅ | All 7 admin settings sections verified (default surface, registration, banner, message bar, footer, vault, email). Dead code removed: `sectionCategory()` pruned of AI/routing/channel vestiges, `PublicSettings()` stripped of chat-era fields (system_prompt, retention_ttl, paste_to_file, allow_user_personas), dead `PolicyDefaults` removed (allow_raw_model_access, default_model), dead policy lookups removed (kb_direct_access), test seed data cleaned. | +| Packages surface | ✅ | Package list loads (17 total/17 enabled), type filters work, enable/disable visible, settings/export buttons present, core package (admin) protected. Removed dead `chat` from CORE_IDS. | -### v0.2.7 — Admin Settings Audit - -| Step | Status | Description | -|------|--------|-------------| -| Admin settings E2E | ⬚ | Verify all surviving admin settings work end-to-end: default surface, registration, banner, message bar, footer, vault, email. Remove any handler/store vestiges of deleted settings (search, compaction, memory). | -| Packages surface | ⬚ | Verify packages page correctly shows installed vs available. Enable/disable works. Broken post-fork surfaces identified and either fixed or marked. | - -### v0.2.8 — User Settings Audit +### v0.2.7 — User Settings Audit | Step | Status | Description | |------|--------|-------------| | User settings E2E | ⬚ | Verify all user settings sections: General (default surface), Appearance (theme, scale, font), Profile (display name, avatar, handle), Connections, Notifications. Remove dead features, fix broken states. | | Visibility gating | ⬚ | Ensure settings sections only show features that are actually available. Hide empty sections. Respect `user_overridable` flag from extension manifests. | -### v0.2.9 — Team Admin Settings Audit (Pass 1) +### v0.2.8 — Team Admin Settings Audit (Pass 1) | Step | Status | Description | |------|--------|-------------| | Team admin E2E | ⬚ | Verify team member management, team settings cascade, role assignment (admin/member). Audit for dead code from pre-fork team features. First pass — validates current functionality before v0.3.x adds team roles. | | User settings team tab | ⬚ | Verify the Teams section in user settings — team list, join/leave, team-scoped settings. | +### v0.2.9 — Builtin Extension Retirement + +| Step | Status | Description | +|------|--------|-------------| +| Retire builtin seeder | ⬚ | Stop auto-seeding chat-centric extensions (csv-table, diff-viewer, js-sandbox, katex-renderer, mermaid-renderer, regex-tester). Remove `SeedBuiltinPackages`, Dockerfile COPY, and `extensions/builtin/` directory. These extensions are not OBE — they're dormant until a chat surface exists to consume them. | +| Convert to regular packages | ⬚ | Repackage the 6 extensions as standard `.pkg` archives in `packages/`. Add chat dependency metadata to manifests so they can be installed when the chat extension ships post-MVP. No auto-install — explicit install only, matching the distribution model. | + ## v0.3.x — Workflow Architecture Workflows are the core platform capability. This series implements the -full multi-step automation system with team role integration. +full multi-step automation system with team role integration and +finalizes the extension lifecycle model. ### v0.3.0 — Workflow Design + Schema | Step | Status | Description | |------|--------|-------------| +| Extension lifecycle | ⬚ | Define permanent vs PoC extensions. Package graduation criteria. Dependency policy. What ships with core vs what's installed separately. | | Workflow design session | ⬚ | Define what "workflow" means in the extension-first model. Determine if the existing `workflows` table/handler survives, gets rebuilt, or gets removed. Document the Starlark contract for multi-step automation. See `docs/DESIGN-WORKFLOW-REDESIGN-0.2.6.md`. | | Team roles | ⬚ | Different roles per team responsible for different workflow stages. Role-based stage assignment, multi-party validation (2-party sign-off at stage boundaries). | | Trigger composition model | ⬚ | How do triggers, schedules, and workflows compose? Event chains, conditional branching, error handling. Design doc before code. | diff --git a/server/database/testhelper.go b/server/database/testhelper.go index d57e2fe..1eb2a23 100644 --- a/server/database/testhelper.go +++ b/server/database/testhelper.go @@ -335,15 +335,13 @@ func TruncateAll(t *testing.T) { INSERT INTO global_settings (key, value) VALUES ('registration', '{"enabled": true}'), ('site', '{"name": "Switchboard Core", "tagline": "Self-hosted extension platform"}'), - ('banner', '{"enabled": false, "text": "", "bg": "#007a33", "fg": "#ffffff"}'), - ('model_roles', '{"utility":{"primary":null,"fallback":null},"embedding":{"primary":null,"fallback":null},"generation":{"primary":null,"fallback":null}}') + ('banner', '{"enabled": false, "text": "", "bg": "#007a33", "fg": "#ffffff"}') ON CONFLICT (key) DO NOTHING `) DB.Exec(` INSERT INTO platform_policies (key, value) VALUES ('allow_user_byok', 'false'), ('allow_user_personas', 'false'), - ('allow_raw_model_access', 'true'), ('allow_registration', 'true'), ('default_user_active', 'false'), ('allow_team_providers', 'true') @@ -354,19 +352,13 @@ func TruncateAll(t *testing.T) { INSERT INTO global_settings (key, value) VALUES ('registration', '{"enabled": true}'::jsonb), ('site', '{"name": "Switchboard Core", "tagline": "Self-hosted extension platform"}'::jsonb), - ('banner', '{"enabled": false, "text": "", "bg": "#007a33", "fg": "#ffffff"}'::jsonb), - ('model_roles', '{ - "utility": { "primary": null, "fallback": null }, - "embedding": { "primary": null, "fallback": null }, - "generation": { "primary": null, "fallback": null } - }'::jsonb) + ('banner', '{"enabled": false, "text": "", "bg": "#007a33", "fg": "#ffffff"}'::jsonb) ON CONFLICT (key) DO NOTHING `) DB.Exec(` INSERT INTO platform_policies (key, value) VALUES ('allow_user_byok', 'false'), ('allow_user_personas', 'false'), - ('allow_raw_model_access', 'true'), ('allow_registration', 'true'), ('default_user_active', 'false'), ('allow_team_providers', 'true') diff --git a/server/handlers/admin.go b/server/handlers/admin.go index 8f4168c..b0ba4e0 100644 --- a/server/handlers/admin.go +++ b/server/handlers/admin.go @@ -361,47 +361,15 @@ func (h *AdminHandler) PublicSettings(c *gin.Context) { // Banner config, branding, etc. — safe subset for non-admin users banner, _ := h.stores.GlobalConfig.Get(c.Request.Context(), "banner") branding, _ := h.stores.GlobalConfig.Get(c.Request.Context(), "branding") - systemPrompt, _ := h.stores.GlobalConfig.Get(c.Request.Context(), "system_prompt") policies, _ := h.stores.Policies.GetAll(c.Request.Context()) - // Paste-to-file threshold (admin-configurable via storage.paste_to_file_chars, default 2000) - pasteChars := 2000 - if storageSettings, err := h.stores.GlobalConfig.Get(c.Request.Context(), "storage"); err == nil { - if v, ok := storageSettings["paste_to_file_chars"]; ok { - switch n := v.(type) { - case float64: - pasteChars = int(n) - case int: - pasteChars = n - } - } - } - - // Only tell the user whether admin prompt exists — don't expose content - hasAdminPrompt := false - if content, ok := systemPrompt["content"].(string); ok && content != "" { - hasAdminPrompt = true - } - - // Retention TTL (v0.37.14 — days before purge for global/team channels) - retentionTTL := 0 - if ttlCfg, err := h.stores.GlobalConfig.Get(c.Request.Context(), "retention_ttl_days"); err == nil { - if v, ok := ttlCfg["value"].(float64); ok { - retentionTTL = int(v) - } - } - c.JSON(http.StatusOK, gin.H{ - "banner": banner, - "branding": branding, - "has_admin_prompt": hasAdminPrompt, - "storage_configured": storageConfigured, - "paste_to_file_chars": pasteChars, + "banner": banner, + "branding": branding, + "storage_configured": storageConfigured, "policies": gin.H{ - "allow_registration": policies["allow_registration"], - "allow_user_byok": policies["allow_user_byok"], - "allow_user_personas": policies["allow_user_personas"], - "retention_ttl_days": retentionTTL, + "allow_registration": policies["allow_registration"], + "allow_user_byok": policies["allow_user_byok"], }, }) } diff --git a/server/handlers/profile_bootstrap.go b/server/handlers/profile_bootstrap.go index 228380f..205f698 100644 --- a/server/handlers/profile_bootstrap.go +++ b/server/handlers/profile_bootstrap.go @@ -86,8 +86,6 @@ func (h *ProfileBootstrapHandler) GetBootstrap(c *gin.Context) { if ps := h.stores.Policies; ps != nil { policies["allow_user_byok"], _ = ps.GetBool(ctx, "allow_user_byok") policies["allow_user_personas"], _ = ps.GetBool(ctx, "allow_user_personas") - policies["allow_raw_model_access"], _ = ps.GetBool(ctx, "allow_raw_model_access") - policies["kb_direct_access"], _ = ps.GetBool(ctx, "kb_direct_access") } // ── Settings ──────────────────────────── diff --git a/server/handlers/profile_permissions.go b/server/handlers/profile_permissions.go index e65a404..a972bca 100644 --- a/server/handlers/profile_permissions.go +++ b/server/handlers/profile_permissions.go @@ -67,8 +67,6 @@ func (h *ProfilePermissionsHandler) GetMyPermissions(c *gin.Context) { if ps := h.stores.Policies; ps != nil { policies["allow_user_byok"], _ = ps.GetBool(ctx, "allow_user_byok") policies["allow_user_personas"], _ = ps.GetBool(ctx, "allow_user_personas") - policies["allow_raw_model_access"], _ = ps.GetBool(ctx, "allow_raw_model_access") - policies["kb_direct_access"], _ = ps.GetBool(ctx, "kb_direct_access") } c.JSON(http.StatusOK, gin.H{ diff --git a/server/models/models.go b/server/models/models.go index 8ff386c..a988d29 100644 --- a/server/models/models.go +++ b/server/models/models.go @@ -84,13 +84,11 @@ type TeamMember struct { // PLATFORM POLICIES var PolicyDefaults = map[string]string{ - "allow_user_byok": "false", - "allow_user_personas": "false", - "allow_raw_model_access": "false", - "allow_registration": "true", - "default_user_active": "false", - "allow_team_providers": "true", - "default_model": "", + "allow_user_byok": "false", + "allow_user_personas": "false", + "allow_registration": "true", + "default_user_active": "false", + "allow_team_providers": "true", } // HandleFromName generates a URL-safe @mention handle from a display name. diff --git a/server/pages/loaders.go b/server/pages/loaders.go index 0cdce61..7cbbd4a 100644 --- a/server/pages/loaders.go +++ b/server/pages/loaders.go @@ -79,16 +79,14 @@ func sectionCategory(section string) string { switch section { case "users", "teams", "groups": return "people" - case "providers", "models", "personas", "roles", "knowledgeBases", "memory": - return "ai" - case "health", "routing", "capabilities": - return "routing" - case "settings", "storage", "packages", "channels", "broadcast": + case "workflows": + return "workflows" + case "settings", "storage", "packages", "broadcast": return "system" case "usage", "audit", "stats": return "monitoring" default: - return "ai" // default landing + return "system" } } diff --git a/src/js/sw/surfaces/admin/packages.js b/src/js/sw/surfaces/admin/packages.js index d0856e9..0a7631a 100644 --- a/src/js/sw/surfaces/admin/packages.js +++ b/src/js/sw/surfaces/admin/packages.js @@ -8,7 +8,7 @@ const { html } = window; const { useState, useEffect, useCallback } = hooks; const TYPE_OPTIONS = ['all', 'surface', 'extension', 'full', 'workflow']; -const CORE_IDS = new Set(['chat', 'admin']); +const CORE_IDS = new Set(['admin']); function typeBadge(type) { const cls = type === 'surface' ? 'badge-active' -- 2.49.1