Feat v0.2.6 admin settings audit #10
26
CHANGELOG.md
26
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
|
||||
|
||||
27
ROADMAP.md
27
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. |
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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"],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 ────────────────────────────
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user