Management Plane Migration Validation: Go to Rust
Date: 2026-03-21
Validator: polecat/furiosa (automated)
Go source: apps/management-plane/ (deleted after validation)
Rust target: rust/management-plane/
Summary
All Go endpoints, services, models, middleware, background workers, and tests have equivalent Rust implementations. Migrations are byte-for-byte identical. The Rust version is a superset of Go (adds Ed25519 token issuance endpoint).
Endpoint Checklist
| Endpoint | Method | Go | Rust | Status |
|---|---|---|---|---|
/management/v1/health | GET | handler/health.go | handlers/health.rs | PASS |
/management/v1/agents/register | POST | handler/registration.go | handlers/registration.rs | PASS |
/management/v1/agents/{agent_id}/heartbeat | POST | handler/heartbeat.go | handlers/heartbeat.rs | PASS |
/management/v1/releases | POST | handler/release.go | handlers/release.rs | PASS |
/management/v1/releases | GET | handler/release.go | handlers/release.rs | PASS |
/management/v1/releases/latest | GET | handler/release.go | handlers/release.rs | PASS |
/management/v1/releases/{version}/{os}/{arch}/download | GET | handler/release.go | handlers/release.rs | PASS |
/management/v1/licenses | POST | handler/license.go | handlers/license.rs | PASS |
/management/v1/licenses | GET | handler/license.go | handlers/license.rs | PASS |
/management/v1/licenses/{id} | GET | handler/license.go | handlers/license.rs | PASS |
/management/v1/licenses/{id} | PUT | handler/license.go | handlers/license.rs | PASS |
/management/v1/licenses/{id} | DELETE | handler/license.go | handlers/license.rs | PASS (fixed) |
/management/v1/licenses/validate | GET | handler/license.go | handlers/license.rs | PASS |
/management/v1/license/status | GET | handler/license_status.go | handlers/license_status.rs | PASS (fixed) |
/management/v1/agents | GET | handler/fleet.go | handlers/fleet.rs | PASS |
/management/v1/agents/{agent_id} | GET | handler/fleet.go | handlers/fleet.rs | PASS |
/management/v1/fleet/stats | GET | handler/fleet.go | handlers/fleet.rs | PASS |
/management/v1/alerts | GET | handler/alert.go | handlers/alert.rs | PASS |
/management/v1/alerts/summary | GET | handler/alert.go | handlers/alert.rs | PASS |
/management/v1/alerts/{id}/acknowledge | PUT | handler/alert.go | handlers/alert.rs | PASS |
/management/v1/webhooks | POST | handler/webhook.go | handlers/webhook.rs | PASS (fixed) |
/management/v1/webhooks | GET | handler/webhook.go | handlers/webhook.rs | PASS (fixed) |
/management/v1/webhooks/{id} | GET | handler/webhook.go | handlers/webhook.rs | PASS |
/management/v1/webhooks/{id} | PATCH | handler/webhook.go | handlers/webhook.rs | PASS (fixed) |
/management/v1/webhooks/{id} | DELETE | handler/webhook.go | handlers/webhook.rs | PASS (fixed) |
/management/v1/webhooks/{id}/deliveries | GET | handler/webhook.go | handlers/webhook.rs | PASS |
/management/v1/webhooks/deliveries | GET | handler/webhook.go | handlers/webhook.rs | PASS |
/management/v1/tokens | POST | N/A | services/token.rs | ADDED |
Issues Found and Fixed
1. Missing auth middleware (CRITICAL)
Problem: Rust middleware.rs defined management_key_auth but it was not applied to any
route group. License CRUD, fleet, alert, and webhook endpoints were all unprotected.
Fix: Restructured main.rs router to split authenticated vs unauthenticated routes.
Applied management_key_auth middleware layer to authenticated group.
2. Response format mismatches
| Endpoint | Go Response | Rust (before) | Rust (after) |
|---|---|---|---|
| DELETE /licenses/{id} | 200 {"message":"license deactivated"} | 204 No Content | 200 {"message":"license deactivated"} |
| PATCH /webhooks/{id} | 200 {"message":"webhook updated"} | 204 No Content | 200 {"message":"webhook updated"} |
| DELETE /webhooks/{id} | 200 {"message":"webhook deleted"} | 204 No Content | 200 {"message":"webhook deleted"} |
| POST /webhooks | 201 {"webhook":..,"secret":..} | 201 (raw webhook) | 201 {"webhook":..,"secret":..} |
| GET /webhooks | {"webhooks":[..]} | raw array | {"webhooks":[..]} |
3. Missing validation
- Webhook create: Added
CreateWebhookRequest.validate()call (was skipped in Rust) - License status: Added empty key check (Go returns 400 for missing key)
Component Parity
Services
| Service | Go | Rust | Status |
|---|---|---|---|
| Registration | service/registration.go | services/registration.rs | PASS |
| Heartbeat | service/heartbeat.go | services/heartbeat.rs | PASS |
| License | service/license.go | services/license.rs | PASS |
| License Status | service/license_status.go | services/license_status.rs | PASS |
| Fleet | service/fleet.go | services/fleet.rs | PASS |
| Alert | service/alert.go | services/alert.rs | PASS |
| Webhook | service/webhook.go | services/webhook.rs | PASS |
| Release | service/release.go | release.rs | PASS |
| Health Checker | service/healthcheck.go | services/healthcheck.rs | PASS |
| Token | N/A | services/token.rs | ADDED |
Middleware
| Middleware | Go | Rust | Status |
|---|---|---|---|
| ManagementKeyAuth | middleware/auth.go | middleware.rs + handlers/mod.rs | PASS |
| AgentOrManagementAuth | middleware/auth.go | handlers/mod.rs | PASS |
| CORS | middleware/cors.go | tower_http::cors | PASS |
| RequestLogger | middleware/logging.go | tower_http::trace | PASS |
| Timeout | middleware/timeout.go | tower_http::timeout | PASS |
Background Workers
| Worker | Go | Rust | Status |
|---|---|---|---|
| Health Checker | goroutine in main.go | tokio::spawn in main.rs | PASS |
| Webhook Delivery | goroutine in main.go | tokio::spawn in main.rs | PASS |
Migrations
All 8 migration pairs are byte-for-byte identical (verified via diff -rq).
Config
| Field | Go (config.Load) | Rust (Config::load) | Status |
|---|---|---|---|
| database_url | DATABASE_URL | DATABASE_URL | PASS |
| port | PORT | PORT | PASS |
| management_key | MANAGEMENT_KEY | MANAGEMENT_KEY | PASS |
| cors_origins | CORS_ORIGINS | CORS_ORIGINS | PASS |
| log_level | LOG_LEVEL | LOG_LEVEL | PASS |
| gcs_bucket | GCS_BUCKET | GCS_BUCKET | PASS |
| signed_url_ttl | SIGNED_URL_TTL_MINUTES | SIGNED_URL_TTL_MINUTES | PASS |
| heartbeat_interval | HEARTBEAT_INTERVAL_SECONDS | HEARTBEAT_INTERVAL_SECONDS | PASS |
| health_check_interval | HEALTH_CHECK_INTERVAL_SECONDS | HEALTH_CHECK_INTERVAL_SECONDS | PASS |
| stale_threshold_multiplier | STALE_THRESHOLD_MULTIPLIER | STALE_THRESHOLD_MULTIPLIER | PASS |
| inactive_threshold_multiplier | INACTIVE_THRESHOLD_MULTIPLIER | INACTIVE_THRESHOLD_MULTIPLIER | PASS |
| webhook_worker_interval | WEBHOOK_WORKER_INTERVAL_SECONDS | WEBHOOK_WORKER_INTERVAL_SECONDS | PASS |
| webhook_worker_batch_size | WEBHOOK_WORKER_BATCH_SIZE | WEBHOOK_WORKER_BATCH_SIZE | PASS |
Cleanup Performed
- Deleted
apps/management-plane/directory - Removed management-plane entry from
go.work - Removed Go dependabot entry from
.github/dependabot.yml - Removed Go management-plane services from
docker-compose.yml - Updated
Makefilemigration path torust/management-plane/migrations - Updated
README.mdto reference Rust implementation
Test Results
All 74 Rust management-plane tests pass after fixes.