flnxi-Wu/lib/license/premium-module-service.ts
2026-06-03 11:37:55 +08:00

745 lines
25 KiB
TypeScript

import { prisma } from '@/lib/prisma';
import { decryptLocal, encryptLocal, hashData, verifySignature } from './crypto-service';
import { generateFingerprint } from './fingerprint-service';
import { buildSignedLicenseRequest } from './request-signature';
import { buildLicenseServerLicenseApiUrl, getLicenseProjectCode, getLicenseServerUrl } from './server-config';
import { getLocalLicense, isLicenseConfigExpired } from './license-service';
import type { LicenseType } from './types';
export interface PremiumModuleManifest {
version: number;
licenseType: LicenseType;
instanceId: string;
modules: Array<{
id: string;
minLicenseType: LicenseType;
artifact: {
type: 'registry-image';
image: string;
digest?: string | null;
version: string;
};
sizeBytes: number;
sha256: string;
entitlementVersion?: number;
limits?: {
maxUsers: number;
maxMaterials: number;
};
}>;
issuedAt: string;
expiresAt: string;
}
interface SignedPremiumModuleManifest {
success: boolean;
manifest?: PremiumModuleManifest;
signature?: string;
error?: string;
}
interface CachedPremiumModuleManifest {
manifest: PremiumModuleManifest;
signature: string;
syncedAt: string;
}
interface PremiumModuleEntitlementResponse {
success: boolean;
entitlement?: {
moduleId: string;
entitlementBase64: string;
sha256: string;
sizeBytes: number;
expiresAt: string;
artifact: PremiumModuleManifest['modules'][number]['artifact'];
};
error?: string;
}
export interface PremiumModulePullCredential {
registryHost: string;
repository: string;
tag: string;
digest: string | null;
username: string;
pullToken: string;
expiresAt: string;
}
interface PremiumModulePullCredentialResponse {
success: boolean;
pullCredential?: PremiumModulePullCredential;
error?: string;
}
export type PremiumModuleRuntimeStatusValue =
| 'missing'
| 'pulling'
| 'running'
| 'unhealthy'
| 'expired'
| 'revoked';
export interface PremiumModuleRuntimeStatus {
moduleId: string;
image: string;
digest: string | null;
status: PremiumModuleRuntimeStatusValue;
lastSyncAt: string;
lastError?: string;
}
interface CachedPremiumModuleEntitlements {
entitlements: Record<
string,
{
entitlementBase64: string;
sha256: string;
sizeBytes: number;
expiresAt: string;
artifact: PremiumModuleManifest['modules'][number]['artifact'];
pullCredential?: PremiumModulePullCredential;
syncedAt: string;
}
>;
}
const PREMIUM_MODULE_CONFIG_KEY = 'premium_module_manifest';
const PREMIUM_MODULE_ENTITLEMENTS_CONFIG_KEY = 'premium_module_entitlements';
const PREMIUM_MODULE_RUNTIME_STATUS_CONFIG_KEY = 'premium_module_runtime_status';
const PREMIUM_MODULE_PULL_USERNAME = 'flnxi-module';
const PREMIUM_MODULE_HEALTH_TIMEOUT_MS = 3000;
export const PREMIUM_MODULE_IDS = {
LICENSE_LIMITS_CORE: 'license-limits-core',
CUSTOM_TEMPLATES_CORE: 'custom-templates-core',
INTRANET_PENETRATION_CORE: 'intranet-penetration-core',
} as const;
export interface AuthorizedUsageLimits {
maxUsers: number;
maxMaterials: number;
}
export interface PremiumModuleRuntimeGateResult {
available: boolean;
moduleId: string;
status?: PremiumModuleRuntimeStatusValue;
message?: string;
}
export interface PremiumModuleAuthorizeResult extends PremiumModuleRuntimeGateResult {
action: string;
}
type PremiumModuleActionContext = Record<string, unknown>;
function isManifestExpired(manifest: PremiumModuleManifest): boolean {
const expiresAt = new Date(manifest.expiresAt).getTime();
return Number.isNaN(expiresAt) || expiresAt <= Date.now();
}
function verifyPremiumModuleManifest(manifest: PremiumModuleManifest, signature: string): boolean {
return verifySignature(JSON.stringify(manifest), signature);
}
function isValidLimitValue(value: unknown): value is number {
return Number.isInteger(value) && (value as number) >= -1;
}
function isValidUsageLimits(limits: unknown): limits is AuthorizedUsageLimits {
if (!limits || typeof limits !== 'object') return false;
const value = limits as Partial<AuthorizedUsageLimits>;
return isValidLimitValue(value.maxUsers) && isValidLimitValue(value.maxMaterials);
}
async function savePremiumModuleManifest(
manifest: PremiumModuleManifest,
signature: string
): Promise<void> {
const cached: CachedPremiumModuleManifest = {
manifest,
signature,
syncedAt: new Date().toISOString(),
};
await prisma.systemConfig.upsert({
where: { key: PREMIUM_MODULE_CONFIG_KEY },
update: { value: encryptLocal(JSON.stringify(cached)) },
create: { key: PREMIUM_MODULE_CONFIG_KEY, value: encryptLocal(JSON.stringify(cached)) },
});
}
async function getCachedPremiumModuleEntitlements(): Promise<CachedPremiumModuleEntitlements> {
try {
const config = await prisma.systemConfig.findUnique({
where: { key: PREMIUM_MODULE_ENTITLEMENTS_CONFIG_KEY },
});
if (!config) return { entitlements: {} };
const cached = JSON.parse(decryptLocal(config.value)) as CachedPremiumModuleEntitlements;
if (!cached.entitlements || typeof cached.entitlements !== 'object') return { entitlements: {} };
return cached;
} catch (error) {
console.warn('[License] 读取授权模块凭证缓存失败:', error);
return { entitlements: {} };
}
}
async function savePremiumModuleEntitlements(cached: CachedPremiumModuleEntitlements): Promise<void> {
await prisma.systemConfig.upsert({
where: { key: PREMIUM_MODULE_ENTITLEMENTS_CONFIG_KEY },
update: { value: encryptLocal(JSON.stringify(cached)) },
create: { key: PREMIUM_MODULE_ENTITLEMENTS_CONFIG_KEY, value: encryptLocal(JSON.stringify(cached)) },
});
}
export async function getCachedPremiumModuleManifest(): Promise<CachedPremiumModuleManifest | null> {
try {
const config = await prisma.systemConfig.findUnique({
where: { key: PREMIUM_MODULE_CONFIG_KEY },
});
if (!config) return null;
const cached = JSON.parse(decryptLocal(config.value)) as CachedPremiumModuleManifest;
if (!cached.manifest || !cached.signature) return null;
if (isManifestExpired(cached.manifest)) return null;
if (!verifyPremiumModuleManifest(cached.manifest, cached.signature)) return null;
const localLicense = await getLocalLicense();
if (!localLicense || isLicenseConfigExpired(localLicense.cachedConfig)) return null;
if (cached.manifest.licenseType !== localLicense.cachedConfig.type) return null;
if (!cached.manifest.instanceId || cached.manifest.instanceId !== localLicense.instanceId) return null;
return cached;
} catch (error) {
console.warn('[License] 读取授权模块缓存失败:', error);
return null;
}
}
function buildPremiumModuleDownloadUrl(moduleId: string): string {
const baseUrl = getLicenseServerUrl();
const projectCode = encodeURIComponent(getLicenseProjectCode());
return `${baseUrl}/api/license/${projectCode}/premium-modules/${encodeURIComponent(moduleId)}`;
}
function buildPremiumModulePullTokenUrl(moduleId: string): string {
return `${buildPremiumModuleDownloadUrl(moduleId)}/pull-token`;
}
function parseModuleImage(image: string): { registryHost: string; repository: string; tag: string } | null {
const [registryHost, ...pathParts] = image.split('/');
if (!registryHost || pathParts.length === 0) return null;
const imagePath = pathParts.join('/');
const lastSlashIndex = imagePath.lastIndexOf('/');
const tagSeparatorIndex = imagePath.lastIndexOf(':');
if (tagSeparatorIndex <= lastSlashIndex) return null;
return {
registryHost,
repository: imagePath.slice(0, tagSeparatorIndex),
tag: imagePath.slice(tagSeparatorIndex + 1),
};
}
function isSameArtifact(
left: PremiumModuleManifest['modules'][number]['artifact'] | null | undefined,
right: PremiumModuleManifest['modules'][number]['artifact'] | null | undefined
): boolean {
if (!left || !right) return false;
return (
left.type === right.type &&
left.image === right.image &&
(left.digest || null) === (right.digest || null) &&
left.version === right.version
);
}
function isCachedEntitlementValid(
cached: CachedPremiumModuleEntitlements,
module: PremiumModuleManifest['modules'][number]
): boolean {
const entitlement = cached.entitlements[module.id];
if (!entitlement) return false;
if (!isSameArtifact(entitlement.artifact, module.artifact)) return false;
if (entitlement.sha256 !== module.sha256) return false;
if (entitlement.sizeBytes !== module.sizeBytes) return false;
if (hashData(entitlement.entitlementBase64) !== module.sha256) return false;
const entitlementExpiresAt = new Date(entitlement.expiresAt).getTime();
return !Number.isNaN(entitlementExpiresAt) && entitlementExpiresAt > Date.now();
}
function isPullCredentialValid(
credential: PremiumModulePullCredential | null | undefined,
module: PremiumModuleManifest['modules'][number]
): credential is PremiumModulePullCredential {
if (!credential) return false;
if (credential.username !== PREMIUM_MODULE_PULL_USERNAME) return false;
const imageParts = parseModuleImage(module.artifact.image);
if (!imageParts) return false;
if (credential.registryHost !== imageParts.registryHost) return false;
if (credential.repository !== imageParts.repository) return false;
if (credential.tag !== imageParts.tag) return false;
if ((credential.digest || null) !== (module.artifact.digest || null)) return false;
if (!credential.pullToken || typeof credential.pullToken !== 'string') return false;
const expiresAt = new Date(credential.expiresAt).getTime();
return !Number.isNaN(expiresAt) && expiresAt > Date.now();
}
function getPremiumModuleServiceBaseUrl(moduleId: string): string {
const envByModuleId: Record<string, string | undefined> = {
[PREMIUM_MODULE_IDS.LICENSE_LIMITS_CORE]: process.env.LICENSE_LIMITS_MODULE_URL,
[PREMIUM_MODULE_IDS.CUSTOM_TEMPLATES_CORE]: process.env.CUSTOM_TEMPLATES_MODULE_URL,
[PREMIUM_MODULE_IDS.INTRANET_PENETRATION_CORE]: process.env.INTRANET_PENETRATION_MODULE_URL,
};
return (envByModuleId[moduleId] || '').trim().replace(/\/+$/, '');
}
function isRuntimeStatusForModule(
status: PremiumModuleRuntimeStatus | null | undefined,
module: PremiumModuleManifest['modules'][number]
): boolean {
if (!status) return false;
if (status.moduleId !== module.id) return false;
if (status.status !== 'running') return false;
if (status.image !== module.artifact.image) return false;
if ((status.digest || null) !== (module.artifact.digest || null)) return false;
return true;
}
async function fetchModuleJson(
baseUrl: string,
path: string,
init: RequestInit = {}
): Promise<{ ok: boolean; status: number; data: any }> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), PREMIUM_MODULE_HEALTH_TIMEOUT_MS);
try {
const response = await fetch(`${baseUrl}${path}`, {
...init,
signal: controller.signal,
});
const data = await response.json().catch(() => null);
return { ok: response.ok, status: response.status, data };
} finally {
clearTimeout(timer);
}
}
async function checkPremiumModuleHealth(moduleId: string, baseUrl: string): Promise<boolean> {
try {
const result = await fetchModuleJson(baseUrl, '/health', { method: 'GET' });
return result.ok && result.data?.moduleId === moduleId && result.data?.status === 'healthy';
} catch (error) {
console.warn(`[License] 授权模块 ${moduleId} 健康检查失败:`, error);
return false;
}
}
async function getPremiumModuleInfo(
moduleId: string
): Promise<PremiumModuleManifest['modules'][number] | null> {
const cached = await getCachedPremiumModuleManifest();
if (!cached) return null;
return cached.manifest.modules.find((item) => item.id === moduleId) || null;
}
async function hasValidPremiumModuleEntitlement(
module: PremiumModuleManifest['modules'][number]
): Promise<boolean> {
const cachedEntitlements = await getCachedPremiumModuleEntitlements();
return isCachedEntitlementValid(cachedEntitlements, module);
}
async function downloadPremiumModuleEntitlement(
module: PremiumModuleManifest['modules'][number],
fingerprint: Awaited<ReturnType<typeof generateFingerprint>>,
manifestExpiresAt: string
): Promise<PremiumModuleEntitlementResponse['entitlement']> {
const signedRequest = buildSignedLicenseRequest('premium-module-download', {
moduleId: module.id,
instanceId: fingerprint.instanceId,
fingerprint,
manifestExpiresAt,
});
const response = await fetch(buildPremiumModuleDownloadUrl(module.id), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(signedRequest),
});
const data = (await response.json()) as PremiumModuleEntitlementResponse;
if (!response.ok || !data.success || !data.entitlement) {
throw new Error(data.error || `授权模块 ${module.id} 授权凭证获取失败`);
}
if (data.entitlement.moduleId !== module.id) {
throw new Error(`授权模块 ${module.id} 返回内容不匹配`);
}
if (!isSameArtifact(data.entitlement.artifact, module.artifact)) {
throw new Error(`授权模块 ${module.id} 镜像地址校验失败`);
}
if (data.entitlement.sha256 !== module.sha256 || hashData(data.entitlement.entitlementBase64) !== module.sha256) {
throw new Error(`授权模块 ${module.id} 授权凭证哈希校验失败`);
}
if (data.entitlement.sizeBytes !== module.sizeBytes) {
throw new Error(`授权模块 ${module.id} 大小校验失败`);
}
return data.entitlement;
}
async function downloadPremiumModulePullCredential(
module: PremiumModuleManifest['modules'][number],
fingerprint: Awaited<ReturnType<typeof generateFingerprint>>
): Promise<PremiumModulePullCredential> {
const signedRequest = buildSignedLicenseRequest('premium-module-pull-token', {
moduleId: module.id,
instanceId: fingerprint.instanceId,
fingerprint,
});
const response = await fetch(buildPremiumModulePullTokenUrl(module.id), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(signedRequest),
});
const data = (await response.json()) as PremiumModulePullCredentialResponse;
if (!response.ok || !data.success || !data.pullCredential) {
throw new Error(data.error || `授权模块 ${module.id} 镜像拉取凭证获取失败`);
}
if (!isPullCredentialValid(data.pullCredential, module)) {
throw new Error(`授权模块 ${module.id} 镜像拉取凭证校验失败`);
}
return data.pullCredential;
}
export async function getCachedPremiumModuleRuntimeStatus(): Promise<Record<string, PremiumModuleRuntimeStatus>> {
try {
const config = await prisma.systemConfig.findUnique({
where: { key: PREMIUM_MODULE_RUNTIME_STATUS_CONFIG_KEY },
});
if (!config) return {};
const cached = JSON.parse(decryptLocal(config.value)) as Record<string, PremiumModuleRuntimeStatus>;
if (!cached || typeof cached !== 'object') return {};
return cached;
} catch (error) {
console.warn('[License] 读取授权模块运行状态失败:', error);
return {};
}
}
async function savePremiumModuleRuntimeStatus(statuses: Record<string, PremiumModuleRuntimeStatus>): Promise<void> {
await prisma.systemConfig.upsert({
where: { key: PREMIUM_MODULE_RUNTIME_STATUS_CONFIG_KEY },
update: { value: encryptLocal(JSON.stringify(statuses)) },
create: { key: PREMIUM_MODULE_RUNTIME_STATUS_CONFIG_KEY, value: encryptLocal(JSON.stringify(statuses)) },
});
}
export async function syncPremiumModules(): Promise<{ synced: boolean; skipped?: boolean; error?: string }> {
try {
const localLicense = await getLocalLicense();
if (!localLicense || isLicenseConfigExpired(localLicense.cachedConfig)) {
return { synced: false, skipped: true, error: '本地许可证不可用' };
}
const fingerprint = await generateFingerprint();
const signedRequest = buildSignedLicenseRequest('premium-modules', {
instanceId: fingerprint.instanceId,
fingerprint,
});
const response = await fetch(buildLicenseServerLicenseApiUrl('premium-modules'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(signedRequest),
});
const data = (await response.json()) as SignedPremiumModuleManifest;
if (!response.ok || !data.success || !data.manifest || !data.signature) {
return {
synced: false,
error: data.error || '授权模块清单同步失败',
};
}
if (data.manifest.licenseType !== localLicense.cachedConfig.type) {
return { synced: false, error: '授权模块清单许可证类型不匹配' };
}
if (!verifyPremiumModuleManifest(data.manifest, data.signature)) {
return { synced: false, error: '授权模块清单签名验证失败' };
}
const cachedEntitlements = await getCachedPremiumModuleEntitlements();
const runtimeStatuses = await getCachedPremiumModuleRuntimeStatus();
const activeModuleIds = new Set(data.manifest.modules.map((moduleInfo) => moduleInfo.id));
for (const moduleInfo of data.manifest.modules) {
const existing = cachedEntitlements.entitlements[moduleInfo.id];
const hasValidEntitlement = isCachedEntitlementValid(cachedEntitlements, moduleInfo);
let nextEntitlement = existing;
if (!hasValidEntitlement) {
const entitlement = await downloadPremiumModuleEntitlement(
moduleInfo,
fingerprint,
data.manifest.expiresAt
);
if (!entitlement) {
throw new Error(`授权模块 ${moduleInfo.id} 授权凭证为空`);
}
nextEntitlement = {
entitlementBase64: entitlement.entitlementBase64,
sha256: entitlement.sha256,
sizeBytes: entitlement.sizeBytes,
expiresAt: entitlement.expiresAt,
artifact: entitlement.artifact,
pullCredential: existing?.pullCredential,
syncedAt: new Date().toISOString(),
};
}
if (!nextEntitlement) {
throw new Error(`授权模块 ${moduleInfo.id} 本地凭证为空`);
}
if (!isPullCredentialValid(nextEntitlement.pullCredential, moduleInfo)) {
nextEntitlement.pullCredential = await downloadPremiumModulePullCredential(moduleInfo, fingerprint);
nextEntitlement.syncedAt = new Date().toISOString();
}
cachedEntitlements.entitlements[moduleInfo.id] = nextEntitlement;
const existingStatus = runtimeStatuses[moduleInfo.id];
const canCarryRuntimeStatus = Boolean(
existingStatus &&
existingStatus.image === moduleInfo.artifact.image &&
(existingStatus.digest || null) === (moduleInfo.artifact.digest || null) &&
existingStatus.status !== 'expired' &&
existingStatus.status !== 'revoked'
);
runtimeStatuses[moduleInfo.id] = {
moduleId: moduleInfo.id,
image: moduleInfo.artifact.image,
digest: moduleInfo.artifact.digest || null,
status: canCarryRuntimeStatus && existingStatus ? existingStatus.status : 'missing',
lastSyncAt: new Date().toISOString(),
lastError: canCarryRuntimeStatus && existingStatus ? existingStatus.lastError : undefined,
};
}
for (const [moduleId, status] of Object.entries(runtimeStatuses)) {
if (!activeModuleIds.has(moduleId)) {
runtimeStatuses[moduleId] = {
...status,
status: 'revoked',
lastSyncAt: new Date().toISOString(),
lastError: '许可证未包含该授权模块',
};
}
}
await savePremiumModuleManifest(data.manifest, data.signature);
await savePremiumModuleEntitlements(cachedEntitlements);
await savePremiumModuleRuntimeStatus(runtimeStatuses);
return { synced: true };
} catch (error) {
const message = error instanceof Error ? error.message : '授权模块清单同步异常';
return { synced: false, error: message };
}
}
export async function getAuthorizedUsageLimits(): Promise<AuthorizedUsageLimits | null> {
const moduleInfo = await getPremiumModuleInfo(PREMIUM_MODULE_IDS.LICENSE_LIMITS_CORE);
if (!moduleInfo || !isValidUsageLimits(moduleInfo.limits)) return null;
if (!(await hasValidPremiumModuleEntitlement(moduleInfo))) return null;
const authorization = await authorizePremiumModuleAction(
PREMIUM_MODULE_IDS.LICENSE_LIMITS_CORE,
'licenseLimits:enforce'
);
if (!authorization.available) return null;
return {
maxUsers: moduleInfo.limits.maxUsers,
maxMaterials: moduleInfo.limits.maxMaterials,
};
}
export async function getCachedModulePullCredential(moduleId: string): Promise<PremiumModulePullCredential | null> {
const cached = await getCachedPremiumModuleManifest();
if (!cached) return null;
const moduleInfo = cached.manifest.modules.find((item) => item.id === moduleId);
if (!moduleInfo) return null;
const cachedEntitlements = await getCachedPremiumModuleEntitlements();
if (!isCachedEntitlementValid(cachedEntitlements, moduleInfo)) return null;
const credential = cachedEntitlements.entitlements[moduleId]?.pullCredential;
return isPullCredentialValid(credential, moduleInfo) ? credential : null;
}
export async function hasPremiumModule(moduleId: string): Promise<boolean> {
const moduleInfo = await getPremiumModuleInfo(moduleId);
if (!moduleInfo) return false;
return hasValidPremiumModuleEntitlement(moduleInfo);
}
export async function isPremiumModuleRunning(moduleId: string): Promise<PremiumModuleRuntimeGateResult> {
const moduleInfo = await getPremiumModuleInfo(moduleId);
if (!moduleInfo) {
return {
available: false,
moduleId,
message: '当前许可证未包含该授权模块',
};
}
if (!(await hasValidPremiumModuleEntitlement(moduleInfo))) {
return {
available: false,
moduleId,
message: '授权模块凭证不可用,请重新验证许可证',
};
}
const runtimeStatuses = await getCachedPremiumModuleRuntimeStatus();
const runtimeStatus = runtimeStatuses[moduleId];
if (!isRuntimeStatusForModule(runtimeStatus, moduleInfo)) {
return {
available: false,
moduleId,
status: runtimeStatus?.status || 'missing',
message:
runtimeStatus?.lastError ||
'授权模块尚未运行,请先通过 Agent 拉取并启动对应模块',
};
}
const baseUrl = getPremiumModuleServiceBaseUrl(moduleId);
if (!baseUrl) {
return {
available: false,
moduleId,
status: runtimeStatus.status,
message: '授权模块服务地址未配置,无法确认模块健康状态',
};
}
if (!(await checkPremiumModuleHealth(moduleId, baseUrl))) {
return {
available: false,
moduleId,
status: 'unhealthy',
message: '授权模块健康检查未通过',
};
}
return {
available: true,
moduleId,
status: runtimeStatus.status,
};
}
export async function hasUsablePremiumModule(moduleId: string): Promise<boolean> {
return (await isPremiumModuleRunning(moduleId)).available;
}
export async function authorizePremiumModuleAction(
moduleId: string,
action: string,
context: PremiumModuleActionContext = {}
): Promise<PremiumModuleAuthorizeResult> {
const gate = await isPremiumModuleRunning(moduleId);
if (!gate.available) {
return {
...gate,
action,
};
}
const baseUrl = getPremiumModuleServiceBaseUrl(moduleId);
try {
const result = await fetchModuleJson(baseUrl, '/authorize', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action, context }),
});
const allowed = result.ok && result.data?.allowed === true && result.data?.moduleId === moduleId;
return {
available: allowed,
moduleId,
action,
status: allowed ? 'running' : 'unhealthy',
message: allowed ? undefined : result.data?.error || '授权模块拒绝该操作',
};
} catch (error) {
const message = error instanceof Error ? error.message : '授权模块操作授权失败';
return {
available: false,
moduleId,
action,
status: 'unhealthy',
message,
};
}
}
export async function updatePremiumModuleRuntimeStatus(status: PremiumModuleRuntimeStatus): Promise<void> {
if (!status.moduleId || !status.image || !status.lastSyncAt) {
throw new Error('授权模块运行状态缺少必要字段');
}
const validStatuses: PremiumModuleRuntimeStatusValue[] = [
'missing',
'pulling',
'running',
'unhealthy',
'expired',
'revoked',
];
if (!validStatuses.includes(status.status)) {
throw new Error('授权模块运行状态无效');
}
const runtimeStatuses = await getCachedPremiumModuleRuntimeStatus();
runtimeStatuses[status.moduleId] = {
moduleId: status.moduleId,
image: status.image,
digest: status.digest || null,
status: status.status,
lastSyncAt: status.lastSyncAt,
lastError: status.lastError,
};
await savePremiumModuleRuntimeStatus(runtimeStatuses);
}