feat: add controlled plugin marketplace lifecycle
Business Plugins CI / check (plugin-admin) (push) Successful in 1m35s
Business Plugins CI / check (subscription-admin) (push) Successful in 1m29s

This commit is contained in:
Qiufeng
2026-08-28 00:51:34 +08:00
parent dc1c799b98
commit 028b505c36
16 changed files with 1149 additions and 45 deletions
+52 -4
View File
@@ -4,7 +4,8 @@ This directory contains the independent administrator-only control plane for
Business Plugins. It is deliberately separate from Sub2API Core and from the
existing `.s2plugin` OpenAI OAuth transport runtime.
The control plane owns the plugin catalog, signed package verification,
The control plane owns the installed-plugin registry, a constrained marketplace
catalog, signed package verification,
revision directories, lifecycle state, encrypted configuration metadata,
menu preview/apply, and its own audit log. Core remains authoritative for
users, administrator roles, balances, subscriptions, billing, and audit
@@ -41,10 +42,13 @@ session and encrypted plugin configuration.
GET /healthz
GET /readyz
POST /login POST /login/2fa POST /logout
GET /api/marketplace POST /api/marketplace/install (JSON: plugin_id, version)
GET /api/me GET /api/plugins GET /api/plugins/{id}
POST /api/plugins/install (multipart field: package)
POST /api/plugins/{id}/install (multipart field: package)
POST /api/plugins/{id}/upgrade (multipart field: package)
POST /api/plugins/{id}/enable|disable|rollback|uninstall
DELETE /api/plugins/{id} (same delete operation as uninstall)
GET|PUT /api/plugins/{id}/config
POST /api/plugins/{id}/menu-preview|menu-apply
POST /api/menu-items/preview|apply (JSON: {"plugin_id":"..."})
@@ -52,9 +56,53 @@ GET /api/audit
```
Every mutation requires the plugin CSRF token and an `Idempotency-Key`. A
mutation returns an operation ID even when it completes synchronously. Failed
installation and upgrade never replace the active revision. Uninstall is
allowed only after disable and removes plugin files, not Core data.
mutation returns an operation ID even when it completes synchronously. A local
upload or marketplace download only verifies and stages the package in the
registry (`disabled` / “已入库,待启用”); it never starts a process. The
administrator must configure the service and click **enable**. Only a
successful health and readiness check changes the plugin to `healthy` and
installs its runtime/menu. Failed installation and upgrade never replace the
active revision. Delete/uninstall is allowed only after disable and removes
plugin files, not Core data.
## Marketplace catalog
The marketplace is server-side only. The browser receives metadata and sends a
plugin ID/version; it never receives an archive URL and cannot request an
arbitrary download. Set `PLUGIN_MARKETPLACE_INDEX` to a local JSON file (the
default) or an HTTPS index URL. Remote indexes and archives are restricted to
the exact hosts in `PLUGIN_MARKETPLACE_ALLOWED_HOSTS`; HTTP is accepted only
for loopback sources in `PLUGIN_ENV=development`. Redirects, credentials,
queries, fragments, oversized responses, path escapes, and hash mismatches are
rejected. The package must still pass the normal manifest signature, file hash,
and Core compatibility checks.
Catalog format (schema version 1):
```json
{
"schema_version": 1,
"source": "internal-release-catalog",
"entries": [
{
"plugin_id": "example.plugin",
"name": "Example Plugin",
"version": "1.0.0",
"description": "Administrator extension",
"archive_url": "example.plugin-1.0.0.s2plugin",
"archive_sha256": "SHA256_OF_ARCHIVE",
"archive_size": 12345,
"publisher_key_id": "publisher-key-id",
"core_api_baseline": "sub2api-0.1.183",
"tested_core_versions": ["0.1.183"],
"capabilities": ["example.v1"]
}
]
}
```
An entry is never an implicit upgrade. If the plugin ID is already registered,
use the existing upgrade flow, then enable it explicitly.
## Plugin package