weopqrst/mooncassette/matcher does not have a README file

    Candidate

    pub(all) struct Candidate {
    index : Int
    provider : String
    model : String
    differing_paths : Array[String]
    } derive(Eq)

    诊断中的一条候选记录。

    Diagnosis

    pub(all) struct Diagnosis {
    policy : MatchPolicy
    cursor : Int
    total : Int
    candidates : Array[Candidate]
    } derive(Eq)

    一次未命中的诊断结果。

    Diagnosis::hint

    fn Diagnosis::hint(self : Diagnosis) -> String

    一行结论,可直接拼进错误消息。

    Diagnosis::lines

    fn Diagnosis::lines(self : Diagnosis) -> Array[String]

    多行报告,便于打印或写进 CI 日志。

    MatchIndex

    pub struct MatchIndex {
    // private fields
    } derive(Eq)

    一批记录的请求指纹索引。

    只覆盖请求:指纹本就不含响应,因此同一份索引在录制阶段与回放阶段都 有效——录制时在末尾追加以保持对齐即可。

    不变量:fingerprints 与对应的记录逐位对齐。一旦错位,匹配就会指向 错误的记录,而这个错误不会表现为崩溃,只会表现为「回放出了另一个响应」, 极难追查。因此本类型只提供两种修改方式:整体重建,或在末尾追加。

    字段是 priv 的:若可公开构造,上面这条不变量就只是注释里的一句话, 任何人都能造一个与记录无关的索引塞进 find_match。这里的可见性就是那条 不变量的执行者。需要读取时用 length()

    MatchIndex::aligned_with

    fn MatchIndex::aligned_with(self : MatchIndex, interactions : ArrayView[
    Interaction
    ]) -> Bool

    索引是否仍然与这批记录逐位对齐。

    判定依据是条数相等。这建立在一条约定上:记录只会被追加,不会被就地 修改。追加会让条数不等、从而暴露出来;就地修改则不会。Session 因此在 每次追加记录时同步索引,使这项约定成为事实而非期望。

    MatchIndex::build

    为一批记录建立索引。

    MatchIndex::length

    fn MatchIndex::length(self : MatchIndex) -> Int

    索引覆盖的记录条数。

    MatchIndex::push

    在末尾追加一条记录对应的指纹,保持逐位对齐。

    MatchPolicy

    pub(all) enum MatchPolicy {
    Exact
    FingerprintOnly
    Subset(Array[String])
    Sequential
    } derive(Eq)

    回放时使用的匹配策略。

    MatchPolicy::name

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

    策略名,用于日志与诊断输出。

    单独提供而不复用 Show:诊断报告需要的是稳定、可断言的短标识, Show 的输出格式属于展示细节,将来可能调整。

    MatchPolicy::scans_by_fingerprint

    fn MatchPolicy::scans_by_fingerprint(self : MatchPolicy) -> Bool

    该策略是否通过整请求指纹筛选候选。

    用来判断「这次匹配值不值得建指纹索引」:ExactFingerprintOnly 逐条 比较整请求指纹,索引能把「每条一次的重算」降为「整表一次」;而 Sequential 根本不比对请求,Subset 比较的是子集投影(投影依赖 keys,不是整请求 指纹),给它们建索引只是白白付一次 O(记录数) 的代价。

    MatchResult

    pub(all) struct MatchResult {
    index : Int
    interaction :
    Interaction

    } derive(Eq)

    一次成功匹配的结果。

    diagnose

    fn diagnose(request :
    Request
    , interactions : ArrayView[
    Interaction
    ], policy : MatchPolicy, cursor : Int, top? : Int) -> Diagnosis

    诊断一次未命中:找出与 request 最接近的记录,以及差在哪些字段。

    调用方应先对 request 做规范化(必要时再脱敏),否则差异里会混进 易变字段,噪声会盖住真正的原因。@recorder.Session::diagnose 已经代劳。

    代价是 O(记录数 × 字段数):它会把请求与每一条记录做一次结构比较。 这是排错路径,不在回放的正常路径上。

    find_match

    cursor 处开始查找匹配的记录。

    查找顺序为 cursor, cursor+1, ..., 末尾, 开头, ..., cursor-1(环形)。 这样既能正确处理「同一请求被录制多次、按调用次序依次回放」, 又能在实际调用次数多于录制次数时复用最早的一条,而不是直接失败。

    返回 None 表示当前 cassette 中没有候选。是否把它当作错误, 由调用方决定(回放模式报错,自动模式回落真实调用)。

    只有当指纹相同而规范请求不同(即真实哈希碰撞)时才抛错: 那是数据层面的异常,静默容忍会掩盖问题。

    index 可选:传入 MatchIndex 可复用已算好的指纹(见 MatchIndex 的说明)。 省略时逐条现算,结果完全一致,只是慢。重复查询同一份 cassette 时应当传入。

    传入的索引只在条数与这批记录相同时才被采用;否则忽略它并逐条现算。 见下方 usable_index 处的说明。

    is_exhausted

    fn is_exhausted(policy : MatchPolicy, cursor : Int, total : Int) -> Bool

    判断「没找到」是不是因为录制已耗尽

    只有 Sequential 会耗尽:它按位置消费,游标越界就意味着录制的条数不够用。 环形模式(Exact / FingerprintOnly / Subset)永远会回卷,因此它们返回 「没找到」一定是确实没有对应记录。

    区分这两种情况的意义在于修法不同:耗尽要补录制,没有对应记录要改请求 或换匹配策略。把前者报成「没有匹配记录」会把用户引向错误的方向。

    空 cassette 不算耗尽:那属于「一条都没录」,报「没有匹配记录」更贴切。