Feat v0.2.6 admin settings audit #10

Merged
xcaliber merged 1 commits from feat/v0.2.6-admin-settings-audit into main 2026-03-27 15:04:14 +00:00
9 changed files with 58 additions and 77 deletions
Showing only changes of commit 64890bf12f - Show all commits

View File

@@ -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

View File

@@ -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. |

View File

@@ -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')

View File

@@ -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"],
},
})
}

View File

@@ -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 ────────────────────────────

View File

@@ -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{

View File

@@ -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.

View File

@@ -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"
}
}

View File

@@ -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'