Feat settings cascade with user_overridable flag

Three-tier settings resolution (global → team → user) with per-key
user_overridable flag in package manifests. Admins can lock settings
that team admins and users cannot override.

- Add package_team_settings table (migration 010)
- Add GetTeamSettings/SetTeamSettings/DeleteTeamSettings to PackageStore
- Add ResolveSettings, ParseSettingsSchema, FilterOverridableKeys
- Rewrite Starlark settings.get() to use cascade resolver
- Add TeamID to RunContext for team-aware resolution
- Enforce user_overridable on user settings save
- Add team package settings API (GET/PUT/DELETE)
- 12 unit tests for cascade resolution

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 17:57:14 +00:00
parent a830ae3684
commit 45448698d6
13 changed files with 664 additions and 30 deletions

View File

@@ -7,6 +7,8 @@ import (
"github.com/gin-gonic/gin"
"strings"
"switchboard-core/database"
"switchboard-core/models"
"switchboard-core/store"
@@ -91,7 +93,27 @@ func (h *ExtensionHandler) UpdateUserExtensionSettings(c *gin.Context) {
settings := json.RawMessage("{}")
if body.Settings != nil {
settings = body.Settings
// v0.2.0: enforce user_overridable — strip locked keys before saving
schema := store.ParseSettingsSchema(pkg.Manifest)
if len(schema) > 0 {
var incoming map[string]any
if json.Unmarshal(body.Settings, &incoming) == nil && len(incoming) > 0 {
filtered, rejected := store.FilterOverridableKeys(incoming, schema)
if len(filtered) == 0 && len(rejected) > 0 {
c.JSON(400, gin.H{"error": "these settings cannot be overridden: " + strings.Join(rejected, ", ")})
return
}
if b, err := json.Marshal(filtered); err == nil {
settings = json.RawMessage(b)
} else {
settings = body.Settings
}
} else {
settings = body.Settings
}
} else {
settings = body.Settings
}
}
pus := &store.PackageUserSettings{