mirror of
https://github.com/saltbo/zpan.git
synced 2026-09-21 13:20:33 +08:00
* feat(licensing): cloud client + binding API (pair, poll, refresh, disconnect) - server/licensing/public-keys.ts — DEV PASERK placeholder (production key lands via C5 cross-repo PR) - server/licensing/verify.ts — verifyCertificate() using paseto-ts/v4, returns LicenseEntitlement | null - server/licensing/entitlement.ts — loadEntitlement() with 60s in-process memoization + invalidateEntitlementCache() - server/licensing/has-feature.ts — loadBindingState() + hasFeature() pure sync check - server/licensing/instance-id.ts — getOrCreateInstanceId() lazily persisted in systemOptions under 'instance_id' - server/licensing/refresh.ts — performRefresh(): calls cloud, verifies cert, rotates DB row; handles CloudUnboundError (clear binding) and CloudNetworkError (update error log, keep cached cert) - server/services/licensing-cloud.ts — createPairing(), pollPairing(), refreshEntitlement() with 10s timeout; CloudUnboundError + CloudNetworkError for typed error handling - server/routes/licensing.ts — public GET /api/licensing/status (no auth required) - server/routes/licensing-admin.ts — admin-only: POST /pair, GET /pair/:code/poll, POST /refresh, DELETE /binding - server/middleware/require-feature.ts — requireFeature(name) middleware, returns 402 when feature missing - server/app.ts — mount /api/licensing (public) + /api/licensing (admin) + export route types - src/lib/rpc.ts — licensingApi + licensingAdminApi RPC clients - src/lib/api.ts — getLicensingStatus(), connectCloud(), pollPairing(), refreshLicense(), disconnectCloud() - src/lib/api.test.ts — 17 new tests covering all 5 new api.ts wrappers - shared/types/licensing.ts — update LicenseEntitlement.issued_at/expires_at to string (ISO-8601) - paseto-ts dependency added for PASETO v4 public verification Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f * fix(licensing): fix biome lint issues — remove unused imports, format test file Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f * fix(licensing): apply biome format fixes to refresh, require-feature, licensing-cloud Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f * test(licensing): add unit and integration tests for all new licensing modules - server/licensing/public-keys.test.ts — PUBLIC_KEYS format validation - server/licensing/verify.test.ts — verifyCertificate: valid cert, invalid sig, expired, instance mismatch, key rotation - server/licensing/has-feature.test.ts — hasFeature: null/unbound/empty/expired/future states - server/services/licensing-cloud.test.ts — createPairing, pollPairing, refreshEntitlement: success, 401 Unbound, network error - server/routes/licensing.integration.test.ts — GET /api/licensing/status: unbound, bound+cert, bound+no-cert, public access - server/routes/licensing-admin.integration.test.ts — auth guards (401/403) + POST /pair, GET /pair/:code/poll, POST /refresh, DELETE /binding - server/test/setup.ts — add license_binding table to in-memory schema Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f * test(licensing): add entitlement cache and refresh orchestration unit tests - entitlement.test.ts — loadEntitlement: no row, no cert, valid PASETO cert, expired cert; invalidateEntitlementCache: re-reads from DB after invalidation - refresh.test.ts — performRefresh: no-op when unbound, rotates token (pre-C5 object), rotates token (PASETO string), clears binding on 401 Unbound, updates error log on network failure Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f --------- Co-authored-by: Bob <aibob@mails.agent-kanban.dev>