Feat v0.8.5 extension composability (#72)
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-go-pg (push) Successful in 2m44s
CI/CD / test-sqlite (push) Successful in 2m54s
CI/CD / build-and-deploy (push) Successful in 52s

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #72.
This commit is contained in:
2026-04-03 11:33:47 +00:00
committed by xcaliber
parent 3c403dd884
commit 98fd3eb3e6
15 changed files with 850 additions and 531 deletions

View File

@@ -37,6 +37,44 @@ var validTiers = map[string]bool{
models.ExtTierSidecar: true,
}
// ValidateComposabilityFields checks that manifest slots and contributes
// fields follow the expected conventions. Slots values must have a
// "description" string. Contributes keys must follow "pkg:slot" naming.
// Returns nil if valid or missing; returns an error string otherwise.
func ValidateComposabilityFields(manifest map[string]any) string {
// Validate slots: map of name → {description, ...}
if slotsRaw, ok := manifest["slots"]; ok {
slots, ok := slotsRaw.(map[string]any)
if !ok {
return "slots must be an object"
}
for name, v := range slots {
entry, ok := v.(map[string]any)
if !ok {
return "slots." + name + " must be an object"
}
if _, ok := entry["description"]; !ok {
return "slots." + name + " must have a description"
}
}
}
// Validate contributes: map of "pkg:slot" → {label, ...}
if contribRaw, ok := manifest["contributes"]; ok {
contribs, ok := contribRaw.(map[string]any)
if !ok {
return "contributes must be an object"
}
for key := range contribs {
if !strings.Contains(key, ":") {
return "contributes key " + key + " must follow pkg:slot convention"
}
}
}
return ""
}
// ── User endpoints ──────────────────────────────
// ListUserExtensions returns all enabled extensions for the current user,
@@ -208,6 +246,12 @@ func (h *ExtensionHandler) AdminInstallExtension(c *gin.Context) {
manifestMap = map[string]any{}
}
// Validate composability fields (slots / contributes)
if errMsg := ValidateComposabilityFields(manifestMap); errMsg != "" {
c.JSON(400, gin.H{"error": "manifest: " + errMsg})
return
}
pkg := &store.PackageRegistration{
ID: body.ExtID,
Title: body.Name,