Feat v0.3.7 package audit
- Fix 6 chat-extension manifests: "name" → "title" (was blocking install) - Add "type": "surface" to hello-dashboard, icd-test-runner, sdk-test-runner - Add dormant status to packages schema (SQLite + Postgres) - Installer auto-detects requires: ["chat"] → sets dormant + disabled - Enable endpoint returns 409 for dormant packages - Relax dependency check: pending_review libraries allowed - Admin UI: dormant badge, disabled Enable button, Dormant stat card - 4 new handler tests (143 total passing) - Docker audit: all 16 packages install correctly Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -94,6 +94,20 @@ func (h *PackageHandler) GetPackage(c *gin.Context) {
|
||||
// PUT /api/v1/admin/surfaces/:id/enable (alias)
|
||||
func (h *PackageHandler) EnablePackage(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
// v0.3.7: Block enabling dormant packages (unmet requires).
|
||||
pkg, err := h.stores.Packages.Get(c.Request.Context(), id)
|
||||
if err != nil || pkg == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "package not found"})
|
||||
return
|
||||
}
|
||||
if pkg.Status == "dormant" {
|
||||
c.JSON(http.StatusConflict, gin.H{
|
||||
"error": "package is dormant — it requires capabilities not yet available (e.g. chat)",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.stores.Packages.SetEnabled(c.Request.Context(), id, true); err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "package not found"})
|
||||
return
|
||||
@@ -533,8 +547,8 @@ func (h *PackageHandler) InstallPackage(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "dependency is not a library: " + libID})
|
||||
return
|
||||
}
|
||||
if lib.Status != "active" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "dependency is not active: " + libID})
|
||||
if lib.Status == "suspended" || lib.Status == "dormant" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "dependency is " + lib.Status + ": " + libID})
|
||||
return
|
||||
}
|
||||
versionSpec, _ := vSpec.(string)
|
||||
@@ -569,14 +583,36 @@ func (h *PackageHandler) InstallPackage(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
// v0.3.7: Auto-set dormant for packages with unmet requires.
|
||||
isDormant := false
|
||||
if reqs, ok := manifest["requires"].([]any); ok && len(reqs) > 0 {
|
||||
for _, r := range reqs {
|
||||
req, _ := r.(string)
|
||||
if req == "chat" {
|
||||
isDormant = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if isDormant {
|
||||
h.stores.Packages.SetStatus(c.Request.Context(), pkgID, "dormant")
|
||||
h.stores.Packages.SetEnabled(c.Request.Context(), pkgID, false)
|
||||
preservedEnabled = false
|
||||
log.Printf("[packages] %s: dormant (unmet requires: chat)", pkgID)
|
||||
}
|
||||
|
||||
resp := gin.H{
|
||||
"id": pkgID,
|
||||
"title": title,
|
||||
"type": pkgType,
|
||||
"version": version,
|
||||
"source": pkgSource,
|
||||
"enabled": preservedEnabled,
|
||||
})
|
||||
}
|
||||
if isDormant {
|
||||
resp["status"] = "dormant"
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// extractableRelPath returns the relative path for a zip entry if it
|
||||
|
||||
Reference in New Issue
Block a user