/** * Fleet worker-side QUOTA-LEASE consumption(调度框架 D4=AP 模式的 worker 半场; * 契约 = registry-core `/fleet` QuotaLease + center `POST /api/fleet/leases`,center 侧已实装并经 * chaos 测试验证)。 * * ## 模型(roaming/CDN) * center 把某 principal 全局预算的一片(默认 $5 slice / 1h TTL)预授权给 ONE worker;worker 本地扣减 * (`recordSpend`,零 per-call 往返),`shouldRenewLease`(任一维度 80% 或寿命 80%)提前后台续租;center 用 * usage 批报事后单调对账(`/api/fleet/usage` 入库顺手 reconcile —— worker 不需要单独上报 consumed)。 * * ## 协商式发现(谁走 lease?) * worker 不在本地解析 EntitlementBudget —— 首次 admit 向 center POST,center 用 resolveEntitlement 判: * - 200 {lease} → 该 principal 是 lease-enforced,进入本地扣减周期; * - 409 "principal is not lease-enforced" → 负缓存 {@link NOT_LEASE_TTL_MS},走既有本地 CostQuota 路径; * - 409 `code:"quota_exhausted"` → 提交拒绝(429,镜像 quotaExceeded 姿势),短缓存防打爆 center; * - 网络/5xx → **fail-open 放行**(AP 哲学:center 短暂不可达绝不停集群;对账兜底, * 过花敞口 ≤ slice 尺寸 —— 正是 D4 承诺的 trade)。 * 单飞(single-flight):同 principal 并发首触/续租只发一个 RTT。 * * ## 边界(documented,非疏漏) * - lease 状态 **in-memory**:worker 重启丢本地 consumed → center 续租时用 reports+monotone guard 补账, * 敞口同样 ≤ slice(AP 设计内)。 * - 409 判别靠 center 返回的结构化 code(两端常量);center 侧变更时此处同步。 * - 全部 swallow-guarded:lease 面绝不 fault 任务提交路径。 */ import { QuotaLease } from "@sema-agent/settings-schema/fleet"; /** 负缓存 TTL:非 lease-enforced 的 principal 多久后再问一次 center(entitlement 升级为 lease 的收敛上界)。 */ export declare const NOT_LEASE_TTL_MS: number; /** center 判 budget exhausted 后,多久内的重复提交直接拒绝而不再打 center(防 per-request 打爆)。 */ export declare const EXHAUSTED_RETRY_MS: number; /** principal 状态表上界(镜像 FLEET_USAGE_KEY_CAP 的姿势):超界丢最旧 —— 被丢的 principal 下次 admit * 重新协商,一次多余 RTT,无正确性影响。 */ export declare const LEASE_PRINCIPAL_CAP = 4096; /** 复审 O1:lease 协商 RTT 的硬上限——leaseDenied 在提交热路径同步 await 它,一个 accept-but-hang 的 * center 必须迅速塌成 fail-open 而不是拖垮提交。取 2.5s(远小于任何续租余量,略紧于 registry 的 8s)。 */ export declare const LEASE_FETCH_TIMEOUT_MS = 2500; /** E4 定稿的 409 结构化字段(center verbatim → 我方 429 body verbatim 透传;人话归壳/web)。 */ export type QuotaDenyDetail = { windowType?: "fiveHour" | "weekly" | "budget"; remaining?: number; resetAt?: string | null; isEstimate?: boolean; pool?: string; }; type PrincipalState = { kind: "lease"; lease: QuotaLease; } | { kind: "not-lease"; until: number; } | { kind: "exhausted"; until: number; detail?: QuotaDenyDetail; }; export type LeaseAdmission = { ok: true; } | { ok: false; reason: "budget_exhausted"; retryAfterSec: number; detail?: QuotaDenyDetail; }; export interface FleetLeaseOpts { center: { baseUrl: string; token: string; worker: string; }; fetchImpl?: typeof fetch; logger?: { info?(msg: string, meta?: Record): void; warn?(msg: string, meta?: Record): void; }; metrics?: { inc(name: string, labels?: Record): void; }; now?: () => number; } export declare class FleetLeaseManager { private readonly opts; private readonly state; private readonly inflight; private readonly f; private readonly base; private readonly headers; private readonly now; private failStreak; constructor(opts: FleetLeaseOpts); /** * Admission check for one billed submission. Fast path is synchronous-shaped (live lease / fresh negative * cache → no await beyond the microtask); only first-touch / expiry / exhaustion pays a center RTT. */ admit(principal: string): Promise; /** Local deduction — wired at the SAME tracer `brain.call` point as the usage accumulator (ALS principal), * sync + never throws (TracerHook contract). No-op unless the principal currently holds a lease. * registry-core 0.6.1 双轴:weightedTokens(加权 tokens 配额轴,weight-at-burn 已在调用方固化) * + costMicroUsd($ 轴,轴名沿用 0.5 既定)。任一轴耗尽=leaseExhausted 拒(deny-wins,registry 类型层定死)。 * * 🔴 [ref](core 2.3.0)已核 undefined 安全:两轴都是 `?? 0`,unpriced 部署下 budget.ts 不带 * costMicroUsd 键调进来 ⇒ $ 轴不扣、不产 NaN。**这是有意的 fail-open**:租约是**执行面**,把"未知" * 当"扣满"会让没配价目表的部署一上来就被 leaseExhausted 拒掉所有请求(deny-wins),那是把可观测性 * 缺口升级成可用性事故。补偿=配额轴(weightedTokens)不依赖价目表、逐笔精确,治理在 unpriced 部署下 * 仍然咬得住;$ 轴对这类部署等于未启用,这一点在部署位 `capabilities.pricingConfigured` 上可读回。 */ recordSpend(principal: string | undefined, d: { weightedTokens?: number; costMicroUsd?: number; }): void; /** Test/observability hook: the current state kind for a principal (never exposes the token). */ peek(principal: string): PrincipalState["kind"] | undefined; private set; private issueOrRenew; private issueOrRenewInner; } /** * Env/config gate(与 startFleetClientFromEnv 同 D3 分层姿势,但 lease 不需要 FLEET_ADVERTISE_ADDRESS —— * 它是出站消费面,不是入站可达性声明):sema-registry lane 配齐(baseUrl+token+worker)即启;任一缺失 → * undefined,非 fleet 部署零行为变化。 */ export declare function createFleetLeaseFromEnv(cfg: { configCenter?: { baseUrl: string; token: string; worker?: string; }; }, opts: Omit): FleetLeaseManager | undefined; export {}; //# sourceMappingURL=fleet-lease.d.ts.map