Feat v0.2.6 admin settings audit (#10)
Some checks failed
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-frontend (pull_request) Failing after 5s
CI/CD / test-go-pg (pull_request) Failing after 2m37s
CI/CD / test-sqlite (pull_request) Successful in 2m51s
CI/CD / build-and-deploy (pull_request) Has been skipped

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) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 15:03:10 +00:00
parent 3cc3360624
commit 64890bf12f
9 changed files with 58 additions and 77 deletions

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