refactor(backend): collapse entitlement provider abstraction back to LicenseService (#889)

Removes backend/src/entitlements/ (registry, loadProvider,
CommunityEntitlementProvider, types, headers, normalize) and the two
abstraction-only tests. Relocates headers/normalize/types to
services/license-*.ts. Swaps 22 consumer call sites from
getEntitlementProvider() to LicenseService.getInstance(). Drops the
Dockerfile install step plus PRO_PACKAGE_VERSION build-arg and
github_token BuildKit secret in docker-publish.yml. Removes the now
stale no-restricted-imports rule in backend/eslint.config.mjs.

Net: 37 files changed, ~700 lines removed, no behavior change. Local
dev no longer requires GitHub Packages auth to start the backend.

Rationale and revisit conditions in
docs/internal/adrs/2026-05-02-collapse-entitlement-provider.md.
This commit is contained in:
Anso
2026-05-02 23:45:44 -04:00
committed by GitHub
parent 6929dad540
commit e5b1c7b22b
39 changed files with 150 additions and 735 deletions
+7 -17
View File
@@ -2,24 +2,14 @@ import crypto from 'crypto';
import axios from 'axios';
import { DatabaseService } from './DatabaseService';
import type {
EntitlementProvider,
LicenseInfo,
LicenseStatus,
LicenseTier,
LicenseVariant,
SeatLimits,
} from '../entitlements/types';
} from './license-types';
import { isLicenseVariant, normalizeVariant } from './license-normalize';
// Header constants and tier/variant normalizers previously exported
// from this file moved to `../entitlements/headers` and
// `../entitlements/normalize` respectively, where they live in the
// public core regardless of which entitlement provider is bound.
// LicenseService imports them back for internal use.
import { isLicenseVariant, normalizeVariant } from '../entitlements/normalize';
// The seat-limit table for paid variants is specific to the
// LemonSqueezy implementation and stays in this file. Phase 2 moves it
// to `@studio-saelix/sencho-pro` along with the rest of the file.
const SEAT_LIMITS: Record<string, SeatLimits> = {
skipper: { maxAdmins: 1, maxViewers: 3 },
admiral: { maxAdmins: null, maxViewers: null },
@@ -143,12 +133,12 @@ export function resolveSenchoVariantFromMeta(
const PROXY_HEADERS_CACHE_TTL_MS = 30_000;
/**
* Implements `EntitlementProvider` so the public core can talk to it
* via `getEntitlementProvider()` without naming this class directly.
* Phase 2 will move this entire file to `@studio-saelix/sencho-pro`,
* at which point the public core only sees the interface.
* Single in-tree license service. Owns Lemon Squeezy validation and
* exposes the tier / variant / seat-limit API consumed across the
* backend. See `docs/internal/adrs/2026-05-02-collapse-entitlement-provider.md`
* for the conditions that would justify reintroducing an interface seam.
*/
export class LicenseService implements EntitlementProvider {
export class LicenseService {
private static instance: LicenseService;
private validationTimer: ReturnType<typeof setInterval> | null = null;
private cachedProxyHeaders: { value: { tier: LicenseTier; variant: LicenseVariant }; expiresAt: number } | null = null;