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
+62
View File
@@ -0,0 +1,62 @@
import type { LicenseTier, LicenseVariant } from './license-types';
/**
* Tier and variant guards / normalizers. Domain knowledge about
* Sencho's tier model (which strings are accepted on input, how legacy
* names map to current names). Used by:
*
* - The proxy layer (`auth.ts`, `remoteNodeProxy.ts`) to parse and
* validate tier/variant headers from inbound forwarded requests.
* - The host-console upgrade handler to decode trusted proxy tier
* claims attached to bearer tokens.
*/
const VALID_TIERS: readonly string[] = ['community', 'paid'] satisfies readonly LicenseTier[];
const VALID_VARIANTS: readonly string[] = ['skipper', 'admiral'] satisfies readonly LicenseVariant[];
/**
* Legacy tier name accepted on input from older proxy headers;
* normalized to the current name on read.
*/
const LEGACY_TIER_MAP: Record<string, LicenseTier> = { pro: 'paid' };
/**
* Legacy variant names accepted on input from older proxy headers;
* normalized to the current names on read.
*/
const LEGACY_VARIANT_MAP: Record<string, Exclude<LicenseVariant, null>> = {
personal: 'skipper',
team: 'admiral',
};
/** Check if value is a recognized tier (current or legacy name). */
export function isLicenseTier(value: unknown): value is string {
return (
typeof value === 'string' &&
((VALID_TIERS as readonly string[]).includes(value) || value in LEGACY_TIER_MAP)
);
}
/** Check if value is a recognized variant (current or legacy name). */
export function isLicenseVariant(value: unknown): value is string {
return (
typeof value === 'string' &&
((VALID_VARIANTS as readonly string[]).includes(value) || value in LEGACY_VARIANT_MAP)
);
}
/**
* Normalize a tier value, mapping legacy names to current equivalents.
* Must be called after `isLicenseTier` validation.
*/
export function normalizeTier(value: string): LicenseTier {
return LEGACY_TIER_MAP[value] ?? (value as LicenseTier);
}
/**
* Normalize a variant value, mapping legacy names to current
* equivalents. Must be called after `isLicenseVariant` validation.
*/
export function normalizeVariant(value: string): Exclude<LicenseVariant, null> {
return LEGACY_VARIANT_MAP[value] ?? (value as Exclude<LicenseVariant, null>);
}