Sign in

    weopqrst/tally/budget does not have a README file

    DigestError

    pub suberror DigestError {
    BadHeader(String)
    MalformedLine(Int, String)
    ReportHashMismatch(String, String)
    PlanHashMismatch(String, String)
    SegmentCountMismatch(Int, Int)
    SegmentActionMismatch(Int, String, String)
    UsedMismatch(Int64, Int64)
    LimitMismatch(Int64, Int64)
    }

    DigestError::message

    fn DigestError::message(self : DigestError) -> String

    给 CLI / 日志用的可读消息。位置信息保留在字符串里,别在调用处再拼一遍。

    FitError

    pub suberror FitError {
    ReserveExceedsWindow(Int64, Int64)
    }

    装配错误(不是「装不下」,装不下会写进报告;这是配置本身不成立)。

    SealError

    pub suberror SealError {
    BadSealHeader(String)
    SealMalformedLine(Int, String)
    SealUnexpectedField(Int, String, String)
    SealSchemeUnsupported(String)
    SealEmptyKey
    SealBadKeyId(String)
    SealDigestMismatch(String, String)
    SealMacMismatch(String, String)
    }

    SealError::message

    fn SealError::message(self : SealError) -> String

    给 CLI / 日志用的可读消息,位置与两个 hex 都保留在字符串里。

    Counter

    pub(all) struct Counter {
    name : String
    exact : Bool
    count_bytes : (Bytes) -> Int64
    }

    计数器:给定任意字节序列,给出 token 数。

    exact=true 表示逐 token 精确计数;exact=false 表示估算, 报告会把它标出来,估算不允许冒充精确。

    Counter::count

    fn Counter::count(self : Counter, bytes : Bytes) -> Int64

    数一段字节的 token 数。

    Counter::estimate

    fn Counter::estimate(chars_per_token : Int) -> Counter

    估算计数器:chars_per_token 个字符当 1 token(向上取整)。

    按字符而不是按字节:中文一个字 3 字节,按字节算会把预算放大 3 倍。 只用于「还没有词表/不知道目标模型」的场合。exact=false 会写进报告。

    DigestEntry

    pub(all) struct DigestEntry {
    index : Int
    action : String
    sha : String
    } derive(
    Debug
    )

    DroppedSegment

    pub(all) struct DroppedSegment {
    index : Int
    role : String
    priority : Priority
    tokens : Int64
    reason : String
    } derive(
    Debug
    )

    被整段丢弃的一段,带原因(这是本层相对同类工具的核心增量)。

    FitConfig

    pub(all) struct FitConfig {
    counter : Counter
    window : Int64
    reserve_output : Int64
    strategy : TruncateStrategy
    pools : Array[PoolQuota]
    }

    一次装配的配置。

    FitConfig::input_limit

    fn FitConfig::input_limit(self : FitConfig) -> Int64

    输入可用预算:window - reserve_output。

    FitConfig::new

    fn FitConfig::new(counter : Counter, window : Int64) -> FitConfig

    默认配置:整窗可用、整段丢弃、无池配额。

    FitConfig::with_pools

    fn FitConfig::with_pools(self : FitConfig, pools : Array[PoolQuota]) -> FitConfig

    设置池配额(按数组顺序处理借用)。

    FitConfig::with_reserve

    fn FitConfig::with_reserve(self : FitConfig, reserve_output : Int64) -> FitConfig

    预留 reserve_output 个 token 给模型回复。

    FitConfig::with_strategy

    fn FitConfig::with_strategy(self : FitConfig, strategy : TruncateStrategy) -> FitConfig

    设置截断策略。

    KeptSegment

    pub(all) struct KeptSegment {
    index : Int
    role : String
    priority : Priority
    tokens : Int64
    bytes : Bytes
    } derive(
    Debug
    )

    被完整保留的一段。

    Plan

    pub(all) struct Plan {
    counter : String
    exact : Bool
    window : Int64
    reserve : Int64
    limit : Int64
    tokens_used : Int64
    pinned_tokens : Int64
    overflow : Bool
    excess : Int64
    pools : Array[PoolUsage]
    kept : Array[KeptSegment]
    truncated : Array[TruncatedSegment]
    dropped : Array[DroppedSegment]
    } derive(
    Debug
    )

    装配计划。tokens_used 是已装入窗口的 token 数; 被丢弃的段不计入,被截断的段只计装入的部分。

    overflow 为真时 tokens_used > limit,超出量在 excess;此时 dropped 末尾还有一行哨兵(index = -1、role = "*overflow*"),它的 tokens 是超出量而不是某个段的原量——按段对账时要跳过它。

    Plan::digest

    fn Plan::digest(self : Plan) -> PlanDigest

    计算一份计划的摘要。entries 按段下标升序,与输入顺序无关。

    Plan::render

    fn Plan::render(self : Plan) -> String

    把计划渲染成规范文本。冻结为格式 tally-plan v3。

    两条格式约束(不新增转义,保持行结构可被空格切分):
    • counter 的名字、段 role 与池名 不得含空格或换行——它们直接进行内;
    • overflow 为真时 [dropped] 末尾有一行哨兵:-1 pinned *overflow* <excess> <原因>, 它不是某个段,按段对账(求和、计数)时要按 index == -1 跳过。

    [pools] 行格式:<池名> <配额> <需求> <占用> <借入> <借出>,按声明顺序。

    Plan::render_digest

    fn Plan::render_digest(self : Plan) -> String

    摘要文本,供 CLI 落盘(与 Plan::render 并列的两个产物)。

    PlanDigest

    pub(all) struct PlanDigest {
    report_sha : String
    plan_sha : String
    entries : Array[DigestEntry]
    } derive(
    Debug
    )

    PlanDigest::render

    fn PlanDigest::render(self : PlanDigest) -> String

    PoolQuota

    pub(all) struct PoolQuota {
    name : String
    limit : Int64
    }

    一个配额池:同名段共享 limit 个 token 的额度。

    语义要点(都写进报告,可核对):
    • 配额只约束可裁段;Pinned 段不裁,若 Pinned 自身超过配额, 报告里 used 会如实大于 quota。
    • 池的额度用不完时,可按 pools 数组的声明顺序借给超额的池; 借用/借出量逐池记账。
    • 没有声明配额的池不参与配额(只受总预算约束)。

    PoolQuota::new

    fn PoolQuota::new(name : String, limit : Int64) -> PoolQuota

    PoolUsage

    pub(all) struct PoolUsage {
    name : String
    quota : Int64
    demand : Int64
    used : Int64
    borrowed : Int64
    lent : Int64
    } derive(
    Debug
    )

    一个配额池的台账。只有 FitConfig::pools 里声明过的池才会出现。

    demand 是该池全部段的原始量,used 是最终装入量;borrowed / lent 是与其他池之间的借用流水。Pinned 段不可裁,所以 used 允许大于 quota(配额只约束可裁段),报告如实呈现。

    Priority

    pub(all) enum Priority {
    Pinned
    Newest
    Middle
    Oldest
    } derive(Eq,
    Debug
    )

    段的丢弃权重,从「不可丢」到「最先牺牲」。

    典型用法:system 提示与工具定义用 Pinned,最近几轮用 Newest, 中间历史用 Middle,最早的历史用 Oldest。 权重相同时先丢原始下标更小的段(更早的历史),因此结果与输入顺序无关可复现。

    Priority::name

    fn Priority::name(self : Priority) -> String

    规范名称(用于可冻结的报告文本,不用 Debug 输出)。

    Priority::weight

    fn Priority::weight(self : Priority) -> Int

    优先级权重;越大越不可丢。

    Seal

    pub(all) struct Seal {
    scheme : String
    key_id : String
    digest_sha : String
    seal : String
    } derive(
    Debug
    )

    Segment

    pub(all) struct Segment {
    role : String
    priority : Priority
    pool : String
    score : Int64
    bytes : Bytes
    }

    上下文里的一段。role 是自由标签(如 system / user), bytes 是该段要送进窗口的内容。

    两个可选维度都只影响取舍,不影响编码:
    • pool —— 配额池名(如 tools / retrieval / history)。同名段共享一份配额, 池用不完的配额可按声明顺序借给超额的池,借用规则会写进报告。 空串表示默认池,不参与配额。
    • score —— 同优先级内的取舍分数,越大越优先保留(检索片段的相关性、 工具定义的重要性都可以放这里)。分数只在同一 Priority 内比较, 不会越过优先级。

    Segment::new

    fn Segment::new(role : String, priority : Priority, bytes : Bytes) -> Segment

    Segment::text

    fn Segment::text(role : String, priority : Priority, text : String) -> Segment

    Segment::with_pool

    fn Segment::with_pool(self : Segment, name : String) -> Segment

    归入某个配额池(name 不得含空格或换行:它会直接进报告)。

    Segment::with_score

    fn Segment::with_score(self : Segment, score : Int64) -> Segment

    设置同优先级内的取舍分数(越大越优先保留)。

    TruncateStrategy

    pub(all) enum TruncateStrategy {
    NoTruncate
    KeepPrefix
    KeepSuffix
    Compress
    } derive(Eq,
    Debug
    )

    预算装不下时对单段的处理方式。

    TruncatedSegment

    pub(all) struct TruncatedSegment {
    index : Int
    role : String
    priority : Priority
    original_tokens : Int64
    tokens : Int64
    strategy : TruncateStrategy
    omitted : Int64
    bytes : Bytes
    } derive(
    Debug
    )

    被截短的一段:只保留一部分字节,tokens 是实际装入的量。

    assemble

    fn assemble(cfg : FitConfig, segments : Array[Segment]) -> Plan

    装配但不校验配置:reserve >= window(甚至 window <= 0)时按实际预算如实记账。

    给 0.3 兼容入口 tally.fit 用——它必须保持旧语义:预算为 0 时全部丢弃、 Pinned 溢出上报,而不是抛错。普通调用请走 fit_with。

    fit_with

    fn fit_with(cfg : FitConfig, segments : Array[Segment]) -> Plan raise FitError

    按配置装配上下文。

    超出预算时的顺序完全确定:先动权重最小的段;权重相同时动原始下标较小的 (更早的历史)。策略为 NoTruncate 时整段丢弃;否则先尝试把该段截短到刚好够用, 截不动(或目标为 0)时才整段丢。

    hmac_sha256_hex

    fn hmac_sha256_hex(key : Bytes, message : Bytes) -> String

    对任意字节序列求 HMAC-SHA256,返回 64 位小写 hex。

    normalize_newlines

    fn normalize_newlines(text : String) -> String

    把文件里的"编辑产物"归一到规范形:去 UTF-8 BOM、CRLF 归一成 LF、结尾只留一个换行。

    为什么需要:格式里只有 LF,但 Windows 控制台重定向会把 LF 写成 CRLF; 编辑器(含 PowerShell 的 Out-File -Encoding utf8)还会在开头写 BOM。 如果不归一,"文件在磁盘上"和"文件被哈希时"就是两份不同的字节, 校验会在没被篡改的文件上失败——那是误报,比漏报更难查。

    parse_digest

    fn parse_digest(text : String) -> PlanDigest raise DigestError

    解析摘要文本。逐行给出错误位置,不做"尽力而为"的修复。

    parse_seal

    fn parse_seal(text : String) -> Seal raise SealError

    解析印记文本。行序固定,键必须一一对上;不做"尽力而为"的容错。

    seal_digest

    fn seal_digest(digest_text : String, key : String, key_id : String) -> String raise SealError

    给摘要盖印记。key 是共享密钥的 UTF-8 字节;空密钥直接拒绝。

    seal_digest_result

    fn seal_digest_result(digest_text : String, key : String, key_id : String) -> Result[String, String]

    盖章的非异常通道:把构造器折成带位置的一句话。

    实际只有 SealEmptyKey / SealBadKeyId 可达(输入问题),其余照列是为了 让 catch 穷尽——同时把"哪些错误存在"集中在 SealError 一处。

    sha256_bytes

    fn sha256_bytes(bytes : Bytes) -> Bytes

    对任意字节序列求 SHA-256,返回 32 字节原始摘要。

    与 sha256_hex 并存是为了 HMAC:它要把内层哈希当字节再喂进外层, 走 hex 再解回来既多余又容易在编码上出岔子。

    sha256_hex

    fn sha256_hex(bytes : Bytes) -> String

    对任意字节序列求 SHA-256,返回 64 位小写 hex。

    verify_digest_pair

    fn verify_digest_pair(report : String, digest_text : String) -> Unit raise DigestError

    只做自洽性校验:报告没有被改过、摘要与报告对得上、used 与逐段求和相等。 要证明"这份报告确实由这批输入和这套策略产出",还需要重跑一遍(CLI 的 --vocab 分支)。

    verify_report

    fn verify_report(report : String, digest_text : String) -> Result[Unit, String]

    不 raise 的校验通道:把 DigestError 折成一句带位置的话。

    存在的理由和 fit_with_result 一样——suberror 构造器不能经 pub using 再导出, 只 import 门面包的使用者写不出 catch { ReportHashMismatch(..) => .. }。 这里把「哪份文件、哪个位置、两个 sha256 各是什么」一次性给全。

    verify_seal

    fn verify_seal(digest_text : String, seal_text : String, key : String) -> Result[Unit, String]

    不 raise 的校验通道:理由同 verify_report(suberror 构造器不能经门面再导出)。

    verify_seal_pair

    fn verify_seal_pair(digest_text : String, seal_text : String, key : String) -> Unit raise SealError

    校验一对「摘要 + 印记」:摘要没被换、印记确实由同一把密钥对这份摘要盖出。