Files
kandev/docs/plans/plugins/task-17-backend-api.md
T
2026-08-24 11:09:03 +00:00

4.2 KiB

id, title, status, wave, depends_on, plan
id title status wave depends_on plan
task-17 Backend HTTP API + proxies + bundle + boot payload + health + wiring done B
task-05
task-06
task-16
task-10
docs/plans/plugins/plan.md

Backend HTTP API + proxies + bundle + boot payload + health + wiring

Title

The full backend surface for native-UI plugins: management + plugin-facing API, auth/capability middleware, state + secrets, webhook proxy, JS bundle proxy, boot-payload active-plugin list, health monitor, and backendapp wiring.

Inputs

  • Spec docs/specs/plugins/requirements/plugins.md (API surface, permissions, security).
  • PLUGIN-API.md: boot payload plugins: [{id,name,bundleUrl,styleUrls?}], bundleUrl=/api/plugins/{id}/bundle; bundle proxied to plugin's ui.bundle.
  • Reuse: task-05 Service (Provide, Registry(), Get, List, Enable/Disable, AuthenticatePlugin, RevealWebhookSecret, StateStore(), SetDeliverer, SetStatus/UpdateLastHealthCheck), task-06 delivery.Deliverer, task-16 manifest ui.bundle.
  • Routing precedent: internal/jira/handlers.go RegisterRoutes(router, dispatcher, svc, log). Reverse-proxy + header stripping precedent: internal/agentctl/server/api/ (read its AGENTS.md).
  • Wiring precedent: internal/backendapp/services.go initJiraService + provideServices; helpers.go routeParams + registerRoutes. Boot payload producer: internal/webapp/payload.go (BootPayload) — add active plugins.

Acceptance

  1. internal/plugins/handlers.go RegisterRoutes(router, svc, deliverer, log):
    • Management: POST /api/plugins/register, GET /api/plugins, GET/:id, PATCH/:id, DELETE/:id, POST/:id/enable, POST/:id/disable. Register returns cleartext creds once.
    • GET /api/plugins/tools (aggregate declared tools; listing only).
    • GET /api/plugins/:id/bundle + GET /api/plugins/:id/ui/* → reverse proxy to plugin base_url (bundle → ui.bundle path). Strip iframe-blocking headers on ui/*; bundle served with Content-Type: text/javascript. Only for active plugins.
    • POST /api/plugins/:id/webhooks/:key → webhook reverse proxy (active + key declared).
  2. internal/plugins/client.go: InvokeTool(ctx, record, tool, input, callCtx) (30s) + Health(ctx, record); internal/plugins/proxy.go: the reverse proxies.
  3. internal/plugins/middleware.go: PluginAuth(svc) (Bearer api_key → plugin identity) + RequireCapability(kind, resource) (403 with capability name). Plugin-facing: state API (/api/plugins/:id/state*, needs state cap, always scoped to authed plugin id), secrets API (/api/plugins/:id/secrets/:ref, needs secrets cap → secretadapter). Office read/write endpoints deferred (comment).
  4. internal/plugins/health.go: HealthMonitor 30s/5s, 3 fails → error + emit plugin.health.degraded bus event, recovery → active + deliverer.Flush(id), updates last_health_check.
  5. Boot payload: internal/webapp/payload.go gains Plugins []ActivePluginPayload ({ID,Name,BundleURL,StyleURLs}) populated from active plugins that have ui.bundle. Gate on features.plugins. Add the mapping wherever office data is mapped into the payload; add a getter on the plugins Service like ActiveUIPlugins() []Record.
  6. Wiring: initPluginsService in services.go calling plugins.Provide(...) with secretadapter.New(secretsStore) as the vault; construct delivery.Deliverer, svc.SetDeliverer(d), start HealthMonitor and deliverer via addCleanup; fields on Services + routeParams; registerRoutes calls plugins.RegisterRoutes(...) gated on p.features.Plugins.

Files

  • apps/backend/internal/plugins/handlers.go, middleware.go, client.go, proxy.go, health.go, dto.go (+ _test.go each)
  • edits: internal/backendapp/services.go, internal/backendapp/helpers.go, internal/webapp/payload.go

Verification

  • go test ./internal/plugins/... ./internal/backendapp/... ./internal/webapp/... (apps/backend)
  • make -C apps/backend build lint

Output contract

Full route table, proxy header behavior, boot-payload shape (so task-18/19 match), wiring points. Ensure make -C apps/backend build test lint pass. This closes the backend.

Dependencies

task-05 (done), task-06, task-16, task-10.