// 多版本自动补全(docs/design/subscriptions.md §2.2):非主版本目标在主版本已满足的单元上补下其他版本。 // 开关按 订阅 → 规则组(链上后者优先)→ 系统缺省(开)取值;补全预算按订阅、按自然日计,超出记延后。 export type AutoFillModeValue = "inherit" | "on" | "off"; export interface AutoFillResolution { enabled: boolean; mode: AutoFillModeValue; source: "subscription" | "rule_group" | "default"; /** source = rule_group 时,决定取值的规则组 */ ruleGroupId?: string; } /** groupFlags:订阅生效规则链上各组的 auto_fill(null = 未设),按链顺序 */ export function resolveAutoFill( mode: AutoFillModeValue | undefined, groupFlags: readonly { id: string; autoFill: boolean | null }[] = [], systemDefault = true, ): AutoFillResolution { if (mode === "on" || mode === "off") return { enabled: mode === "on", mode, source: "subscription" }; for (let k = groupFlags.length - 1; k >= 0; k--) { const g = groupFlags[k]!; if (g.autoFill !== null) return { enabled: g.autoFill, mode: "inherit", source: "rule_group", ruleGroupId: g.id }; } return { enabled: systemDefault, mode: "inherit", source: "default" }; } export interface FillBudgetLimits { bytesPerDay?: number | undefined; unitsPerDay?: number | undefined; } export interface FillBudgetUsage { bytesToday: number; unitsToday: number; } export interface FillBudgetDecision { allow: boolean; violations: { kind: "bytes" | "units"; limit: number; current: number; requested: number }[]; } /** 补全预算:未设的维度不限;请求体积未知时只看单元数(体积由全局预算的 unknown 规则兜底) */ export function checkFillBudget( limits: FillBudgetLimits | undefined, usage: FillBudgetUsage, request: { bytes?: number | null | undefined; units: number }, ): FillBudgetDecision { const violations: FillBudgetDecision["violations"] = []; if (limits?.unitsPerDay !== undefined && usage.unitsToday + request.units > limits.unitsPerDay) violations.push({ kind: "units", limit: limits.unitsPerDay, current: usage.unitsToday, requested: request.units, }); if ( limits?.bytesPerDay !== undefined && request.bytes !== null && request.bytes !== undefined && usage.bytesToday + request.bytes > limits.bytesPerDay ) violations.push({ kind: "bytes", limit: limits.bytesPerDay, current: usage.bytesToday, requested: request.bytes, }); return { allow: violations.length === 0, violations }; }