2026-10-20 AI Agent 面试训练
今日学习主题
作用域与有效期:凭证、约定与状态失效时,如何被正确感知与传播。
今天 5 道题共用一条主线。一个系统里到处都是”带有效期和作用域的东西”:异常的根因链、项目约定文件、对外发布的发现卡片、TLS 证书与会话票据、以及链表里那个把尾指针接回前面的环。它们失效时往往不会报错,只是继续以”看起来还在工作”的方式运行——旧会话继续通信、卡片继续指向旧端点、约定继续被忽略、错误继续被当成”无数据”。真正决定系统可靠性的,不是这些凭证被创建得多规范,而是失效能不能被感知、由谁感知、感知之后传播到哪一层、以及有没有一个在概率性组件之外的确定性执行点来兜底。
题型分布
| 组别 | 数量 | 编号 |
|---|---|---|
| 非 AI Agent 基础定义题 | 1 | B01 |
| AI Agent / LLM 基础定义题 | 1 | B02 |
| AI Agent 真实研发场景题 | 1 | S01 |
| 非 AI Agent 真实研发场景题 | 1 | S02 |
| LeetCode(Easy / Medium) | 1 | L01 |
题目速览
| 编号 | 题目名称 | 题型 | 一级类型 | 难度 | 一句话考察点 |
|---|---|---|---|---|---|
| B01 | 把底层异常翻译成上层错误时,根因去哪了 | 非 Agent 基础 | 编程语言与运行时 | 基础题(中高) | 异常链的两种语义、错误翻译必须保留根因并重新判定可重试性 |
| B02 | AGENTS.md 能约束 Agent 吗 | Agent 基础 | 上下文工程与能力封装 | 基础题(中高) | 项目级约定的作用域、就近覆盖与”约定不是执行约束”的边界 |
| S01 | 跨组织开放 MCP 服务:Server Card 与 AI Catalog 的接入设计 | Agent 场景 | 工具与互操作协议 / AI 安全与治理 | 场景题(高) | 预连接发现与运行时权威值的分工、卡片漂移与信任边界 |
| S02 | 证书轮换后旧会话仍在通信,吊销 40 分钟才生效 | 非 Agent 场景 | 操作系统与网络 | 场景题(高) | TLS 1.3 会话恢复不重新认证证书,会话寿命才是真正的安全边界 |
| L01 | 142. 环形链表 II | LeetCode | 链表 / 快慢指针 | medium | Floyd 判圈两阶段推导与 O(1) 空间找入环点 |
B01 · 非 AI Agent 基础定义题
- 题目 ID:
Q-20261020-B01 - 一级类型:编程语言与运行时
- 二级主题:错误处理、异常链与资源安全
- 考察点:
- 异常链的两种语义(隐式
__context__与显式__cause__)及其在故障定位中的价值。 - 错误翻译(error translation)必须保留根因,并显式重新判定可重试性。
- 异常安全:资源释放必须落在
finally/with,且清理路径本身不能吞掉异常。 - 静默失败:捕获后不处理也不传播,会把”失败”降级为”无数据”。
- 可观测性落点:日志与指标应记在最终处理点,避免逐层记录造成噪声放大。
- 异常链的两种语义(隐式
题目
一个订单服务里,库存查询依赖一个 InventoryClient。最近线上出现一种现象:订单接口返回 200,但订单里的库存字段是空的,下游对账发现这批订单的库存从未被校验过,最终发出了一批超卖订单。
排查后定位到这段代码:
def get_stock(sku: str) -> int | None: try: resp = inventory_client.query(sku, timeout=2.0) return resp["stock"] except Exception: logger.warning("inventory query failed") return None调用方看到 None 就当作”该 SKU 暂无库存记录”,继续走后续流程。
请回答:
- 这段代码在异常语义上犯了哪几个具体错误?
- 异常链(exception chaining)的两种语义分别是什么,什么时候该用哪一种?
- 在分层架构里,“翻译异常”的正确做法是什么,需要同时保留和重新判定什么?
- 与之相近的概念(返回值错误、日志、
finally中的return)边界在哪里?
先思考
- 先区分两件事:“我处理了这个错误”和”我让这个错误消失了”。代码里的
return None属于哪一种? - 再看信息损失:
except Exception捕获时,你能区分”超时”、“连接被拒”、“参数非法”、“权限不足”吗?如果不能,调用方凭什么判断该重试还是该放弃? - 然后想责任归属:谁应该决定”库存查不到时订单该怎么办”?是
get_stock这个库函数,还是调用它的业务层? - 最后想清理:如果
query内部持有一个连接或锁,异常抛出后它会被释放吗?finally里写return会发生什么?
参考答案
结论:异常处理的失败不是”抛错了”,而是根因被丢弃、可重试性被抹平、失败被伪装成正常数据——这段代码把一次确定失败转换成了一个合法的空值,让故障跨过了业务边界而不触发任何告警。
定义。异常处理是错误沿调用栈的传播机制,包含三个动作:
- 抛出:产生一个携带类型、消息与上下文的错误对象,表示”本层无法完成自己的契约”。
- 捕获:决定是处理(恢复、补偿、终止流程)、翻译(转换成上层语义的错误)还是传播。
- 链接:在
except块内再抛新异常时,Python 会把正在处理的异常附加到新异常上。官方文档给出两种语义:- 隐式链接:新异常自动带
__context__,回溯里打印During handling of the above exception, another exception occurred:。 - 显式链接:
raise NewError(...) from exc设置__cause__,回溯里打印The above exception was the direct cause of the following exception:。官方说明这适用于转换异常的场景。 raise NewError(...) from None用于显式禁用自动链接。
- 隐式链接:新异常自动带
适用边界。异常适合”当前层无法完成契约、需要中止当前控制流”的场景;不适合可预期的、需要调用方逐分支处理的业务分支(用返回值或 Result 类型更合适)。跨进程边界时异常类型必然丢失,必须在边界层翻译成稳定的错误码契约。
简洁示例:
class InventoryUnavailable(Exception): def __init__(self, sku: str, retryable: bool): super().__init__(f"inventory unavailable for {sku}") self.sku = sku self.retryable = retryable
def get_stock(sku: str) -> int: try: resp = inventory_client.query(sku, timeout=2.0) except (TimeoutError, ConnectionError) as exc: # 翻译:保留根因链,同时重新判定可重试性 raise InventoryUnavailable(sku, retryable=True) from exc except PermissionError as exc: raise InventoryUnavailable(sku, retryable=False) from exc return resp["stock"]关键变化:不再返回 None,失败就是失败;from exc 保留 TimeoutError 作为根因;retryable 是显式重新判定的结果,上层重试器只看这一个字段。
与相近概念的区别:
| 概念 | 边界 |
|---|---|
| 异常 vs 返回值错误 | 异常强制沿栈寻找 handler,适合”罕见且必须中止”;返回值 / Result[T, E] 适合预期内的失败分支,能被类型系统表达,调用方无法忽略 |
except Exception vs except BaseException | 后者会捕获 KeyboardInterrupt / SystemExit,导致进程无法优雅退出;裸 except: 还会掩盖 AttributeError 这类编程错误 |
| 日志 vs 错误处理 | 日志不恢复控制流,也不改变程序的后续行为;“记一条 warning 然后继续”不是处理,是遗忘 |
finally 中的 return | 会丢弃正在传播的异常,让函数”正常返回”,是最隐蔽的吞异常写法之一 |
典型应用(通用工程):
- 分层架构中的错误翻译:数据访问层抛
TimeoutError→ 领域层抛InventoryUnavailable(retryable=True)→ API 边界层映射为503且带Retry-After;参数非法映射为422且不可重试。 - 重试与退避:重试器按异常类型或错误契约的
retryable字段决策,而不是按 HTTP 状态码反推。 - 资源安全:连接、锁、文件句柄用
with/try...finally保证释放;清理路径自身不得return。 - 并发聚合:多个子任务同时失败时用
ExceptionGroup与add_note保留全部子异常,而不是只抛第一个。
面试现场回答(1–2 分钟):
这段代码的核心问题不是没捕获异常,而是把一次确定的失败转换成了合法的空值。调用方无法区分”没有库存记录”和”查询失败了”,于是故障以 200 的形式穿过了系统,告警完全不会触发。
正确做法有三条。第一,失败不降级为数据:库存查不到就抛领域异常,让业务层显式决定是拒绝下单还是走人工复核。第二,翻译异常时保留根因链,用
raise ... from exc显式声明因果,这样回溯里能一路看到TimeoutError;同时要重新判定可重试性,不能让上层的重试器去猜——超时和连接错误可重试,权限不足不可重试。第三,日志和指标记在最终处理点,不要每层都记,否则同一个错误在日志里出现五次,噪声和成本都放大,根因反而被埋在中间层。还有两个容易忽略的点:
except Exception不能写成except BaseException,否则会吃掉KeyboardInterrupt让服务无法优雅退出;finally里不要return,它会把正在传播的异常直接吞掉。
面试官追问:在 asyncio 结构化并发里,多个子任务同时失败时怎么保留全部根因?
回答要点:单个异常只能承载一条因果链,多失败场景要用 ExceptionGroup / BaseExceptionGroup 聚合,配合 except* 分派处理;用 add_note() 为每个子异常附加任务标识、参数摘要与取消原因。同时要区分”业务失败”与”取消”——CancelledError 不是业务错误,不能和业务异常混在同一组里被重试器消费,否则一次取消会被当成一次真实失败并触发无效重试。
关键点
- 失败不得降级为数据:返回
None/ 空列表 / 默认值,会让下游无法区分”没有”和”失败”,这是静默失败最常见的形态。 - 翻译必须保留根因:
raise X from exc建立显式因果;from None只在根因确实对外无意义时(如隐藏内部实现细节)使用,不是默认选项。 - 可重试性要显式重新判定:下层异常的可重试性不等于上层语义的可重试性,翻译时必须给出明确结论,否则重试器只能猜。
- 清理路径不能吞异常:
finally里不要return,不要抛新异常覆盖正在传播的异常;资源释放应写成with或contextlib上下文管理器。 - 观测记在最终处理点:逐层
logger.error会让同一错误重复计数,指标要按”最终处理点”打点,才能反映真实故障数。
常见误区
except Exception: pass或记日志后返回默认值。错误:把确定性失败转成合法数据,故障不再触发任何告警,且污染下游数据(本例直接导致超卖)。正确做法是让失败成为失败,由业务层显式决策。- 每层捕获后
logger.error再raise。错误:同一错误在日志中出现 N 次,噪声放大 N 倍,且根因被埋在大量中间层堆栈里;指标若按日志计数会严重高估故障量。应在最终处理点记录一次,中间层只做翻译与上下文补充。 - 对所有异常无差别重试。错误:参数非法、权限不足、schema 校验失败这类不可重试错误,重试只会放大无效流量并延长故障恢复时间。可重试性必须是错误契约的一等字段。
- 用
except BaseException或裸except:兜底。错误:会捕获KeyboardInterrupt与SystemExit,使服务无法响应终止信号;也会掩盖本应立刻暴露的编程错误。
延伸学习
- Python 官方教程《8. Errors and Exceptions》§8.5 Exception Chaining、§8.10 Enriching Exceptions with Notes:https://docs.python.org/3/tutorial/errors.html(已核验:包含
from子句、__cause__/__context__两条回溯提示语原文,以及raise ... from None与add_note/ExceptionGroup示例) - 实践方向:在自己项目里挑一条调用链,把”异常类型 → 可重试性 → HTTP 状态码 → 指标标签”整理成一张显式映射表,检查是否存在返回默认值的静默失败路径。
B02 · AI Agent 基础定义题
- 题目 ID:
Q-20261020-B02 - 一级类型:上下文工程与能力封装
- 二级主题:AGENTS.md 等项目级约定的作用域、加载规则与适用边界
- 考察点:
- AGENTS.md 的定位、层级与就近覆盖规则。
- “约定是提示性上下文,不是执行约束”——授权边界必须置于模型之外。
- AGENTS.md 与 README.md、Agent Skill、运行时策略执行点的分工。
- 约定内容膨胀的上下文成本与指令冲突风险。
- 硬约束的正确落点:可执行真相源与模型外策略执行点。
题目
团队把一批硬性规则写进了仓库根目录的 AGENTS.md,包括”禁止直接修改生产配置""所有 PR 必须先跑通集成测试""任何密钥不得写入代码""数据库变更必须走迁移脚本”。负责人的判断是:规则写进去了,Agent 就会遵守。
上线两周后发生一次事故:一个编码 Agent 在一次跨仓库修改中直接改了生产环境的配置项,并且提交时跳过了集成测试。事后复盘发现,出问题的那个子仓库里也有一份 AGENTS.md,内容相对简略,没有包含根目录里的那几条硬规则。
请回答:
AGENTS.md到底是什么,它的作用域和加载规则是什么?为什么子仓库里那份简略的文件会让根目录的规则失效?- 它能不能承担”禁止直接修改生产配置”这类硬性约束?如果不能,这类约束应该落在哪里?
- 它与
README.md、Agent Skill、运行时策略执行点分别是什么关系?
先思考
- 先想”机制”:写一个文件,能让另一个系统一定执行某件事吗?如果不能,它影响的是哪个环节?
- 再想”层级”:两份同名文件同时存在时,是合并还是覆盖?这个细节直接解释了事故成因。
- 然后想”责任边界”:一条安全规则,放在模型输入侧和放在系统侧,失败模式有什么不同?
- 最后想”成本”:约定越长越好吗?它占的是什么资源?
参考答案
结论:AGENTS.md 是给编码 Agent 的上下文,不是控制平面。它能提高”Agent 知道该怎么做”的概率,无法提供”Agent 一定这样做”的保证;因此它只能承载项目级说明与操作指引,硬性约束必须有模型之外的执行点。
定义。官方规范把 AGENTS.md 定位为”给 Agent 的 README”:一个专门的、可预期的位置,用来提供让编码 Agent 能在项目上有效工作的上下文与指令。它与 README.md 是刻意分开的两份文件——README.md 面向人(快速开始、项目说明、贡献指南),AGENTS.md 面向 Agent(构建步骤、测试命令、代码约定),官方说明这样做的理由是给 Agent 一个清晰可预期的指令位置,同时保持 README 简洁。
加载与作用域。根目录放一份;对于大仓或 monorepo,官方建议在每个子项目里再放嵌套的 AGENTS.md,并明确规则:Agent 会自动读取目录树中最近的那份文件,最近的一份优先(closest takes precedence),因此每个子项目都可以下发适合自己的指令。这解释了本事故的成因——子仓库里那份简略的文件覆盖了根目录的约定,而不是与之合并,根目录里的三条硬规则在这次任务中根本没有被加载。
适用边界。
- 适合承载:构建与测试命令、代码风格、目录结构说明、提交与 PR 格式、常见坑、以及如何运行各类检查。
- 不适合承载:安全边界、权限判定、合规强制项、不可逆操作的准入控制。这些内容放在模型输入侧,其保证级别只是”提示”,失败模式是静默违规——Agent 不读、读了不遵守、上下文被压缩后丢失、或被更强的即时指令覆盖,任何一种都会让规则失效,而且不会留下任何可检测的信号。
- 上下文成本:约定文件是常驻上下文的一部分,越长越挤占有效工作区;多份文件之间以及与 Skill 之间的指令冲突,会让模型在没有仲裁机制的情况下自行取舍。
简洁示例:
## 构建与测试- 安装依赖:`pnpm install`- 单元与集成测试:`pnpm test`(**提交前必须通过,CI 会强制校验**)- 类型检查:`pnpm lint`
## 约定- TypeScript strict 模式;单引号、无分号。- 数据库变更只允许通过 `migrations/` 下的脚本,禁止手工改表。- 生产配置不入库,本地不要生成 `.env.production`;需要改配置走 `ops/config-change` 流程。配套的是可执行真相源:CI 里把 pnpm test 配成 required check 并开启分支保护;生产配置仓库设置写入审批与审计;密钥扫描放进 pre-commit 与 CI。AGENTS.md 里只写”规则存在”和”怎么运行对应检查”,不把规则本身作为唯一载体。
与相近概念的区别:
| 概念 | 定位 | 加载方式 | 作用域 | 能否作为硬约束 |
|---|---|---|---|---|
README.md | 面向人的项目说明 | 人主动阅读 | 单个仓库 | 否 |
AGENTS.md | 面向 Agent 的项目说明 | 常驻、无条件加载,目录树中最近者优先 | 仓库 / 子项目 | 否(提示性) |
| Agent Skill | 可复用的能力包与操作手册 | 按需触发,按描述匹配后渐进式披露(元数据 → 正文 → 资源) | 跨项目可复用 | 否 |
| 运行时策略执行点 | 系统侧的授权与校验 | 每次调用强制执行 | 全系统 | 是 |
在 Agent 系统中的应用:
- 上下文分层:把
AGENTS.md作为项目约定层放进上下文组装的固定位置,让它指向命令与检查,而不是重复命令内容。 - 单一真相源:命令与规则只维护在 Makefile / CI 配置 / lint 规则里,
AGENTS.md引用它们,避免双份维护导致漂移。 - 硬约束外置:按最小代理原则,授权边界应置于模型之外——准入、审批、鉴权、不可逆操作的拦截都放在 CI、网关、服务端策略里,
AGENTS.md只负责让 Agent 知道这些门禁存在并知道怎么通过。 - 可检测性:如果一条规则只能靠”Agent 记得”来保证,它就不算被实现了;检验标准是——Agent 违反它时,系统会不会自动阻断。
面试现场回答(1–2 分钟):
AGENTS.md是给编码 Agent 的 README,一个专门放项目上下文和指令的文件,和给人看的 README 是刻意分开的。加载规则上有个关键细节:大仓里可以在每个子项目再放一份,Agent 读的是目录树里最近的那份,最近者优先,是覆盖不是合并。所以这次事故里,子仓库那份简略的文件把根目录的硬规则覆盖掉了,规则根本没进上下文。但更根本的问题是定位。
AGENTS.md属于提示性上下文,它在模型输入侧,只能提高 Agent 遵守的概率,给不出任何保证。Agent 可能不读、读了不遵守、上下文被压缩后丢失,或者被更强的即时指令覆盖——这四种失败都不会留下可检测的信号,也就是静默违规。所以”禁止直接改生产配置”这种硬性约束,正确的落点是在模型之外:生产配置仓库的写入审批与审计、CI 的 required check 与分支保护、密钥扫描。按最小代理原则,授权边界本来就该放在模型之外。实践中我会把
AGENTS.md定位成”指向可执行真相源的索引”:命令和规则只维护在 Makefile、CI 配置、lint 规则里,约定文件只写规则存在和怎么跑对应检查。这样既避免了两处维护必然产生的漂移,也控制了常驻上下文的长度。最后给一个判据:如果一条规则只能靠 Agent 记得来保证,那它就不算被实现了——要看 Agent 违反时系统会不会自动阻断。
面试官追问:如果 AGENTS.md 和某个 Skill 里的规则冲突,Agent 会听谁的?
回答要点:协议层面没有定义仲裁机制,实际行为取决于客户端的上下文组装顺序与指令优先级——也就是说”谁赢”是实现细节,不应该被依赖。工程上的正确做法是消除冲突源而不是赌优先级:规则只保留一份真相源(CI / lint / 服务端策略),约定文件与 Skill 都只做引用;如果确实需要 Skill 覆盖项目默认约定,必须显式声明覆盖范围并在 CI 里做一致性检查。凡是依赖”指令优先级”来保证的正确性,都会在换模型、换客户端、上下文重排之后失效。
关键点
- 约定不是执行约束:
AGENTS.md在模型输入侧,失败模式是静默违规;硬性约束必须有模型外的策略执行点(CI 门禁、分支保护、服务端授权、网关策略)。 - 就近覆盖而非合并:嵌套
AGENTS.md中最近者优先,子项目文件会覆盖根文件的对应约定,大仓需要为每份子文件补齐关键项或做一致性检查。 - 单一真相源:命令与规则应维护在可执行处(Makefile / CI / lint),约定文件只引用,避免双份维护必然产生的漂移。
- 上下文有成本:常驻约定越长越挤占工作区,多份约定之间的冲突没有协议级仲裁,应主动精简。
- 可检测性判据:检验一条规则是否真正落地的标准,是”Agent 违反时系统是否会阻断”,而不是”规则是否被写进了某个文件”。
常见误区
- 把安全与合规规则只写进
AGENTS.md。错误:这等于把授权边界放进模型里,违反最小代理原则与”授权边界应置于模型之外”;一旦上下文未加载或被覆盖,违规不会留下任何信号。正确做法是规则落在 CI、网关与服务端策略,约定文件只做指引。 - 在
AGENTS.md里复制 CI / Makefile 的命令细节。错误:形成两个真相源,任何一次改动都会产生漂移,而漂移的方向通常是”文档还写着旧命令”,反而误导 Agent。应只写引用与运行方式。 - 认为嵌套文件是叠加关系。错误:官方规则是最近者优先(closest takes precedence),是覆盖;把子项目的简略文件当成”补充说明”会直接丢掉根文件的约束。
延伸学习
- AGENTS.md 官方站点:https://agents.md/(已核验:定位原文 “Think of AGENTS.md as a README for agents”;与 README 分开的三条理由;嵌套文件 “Agents automatically read the nearest file in the directory tree, so the closest one takes precedence”;页面称已被 6 万+ 开源项目采用)
- Agent Skills 规范:https://agentskills.io/specification(上一轮已核验:三层渐进式披露,元数据约 100 token / 正文 < 5000 token / 资源按需加载;本轮未重新抓取,具体阈值以官方页面当前内容为准)
S01 · AI Agent 真实研发场景题
- 题目 ID:
Q-20261020-S01 - 一级类型:工具与互操作协议 / AI 安全与治理
- 二级主题:跨组织 MCP 服务对外发布——Server Card 与 AI Catalog 的接入设计、卡片漂移与信任边界
- 核心考点:
- 预连接发现(Server Card / AI Catalog)与运行时权威值(
server/discover与 list 操作)的职责分工。 - 静态清单必然漂移,卡片 MUST NOT 作为安全或访问控制依据。
- 卡片承载位置的取舍(为什么不是
.well-known,为什么不是裸 streamable HTTP 端点)。 - 撤销与失效传播:三层缓存让”发布即生效”不成立。
- 三层能力归属:模型、Agent 编排、确定性系统各自负责什么。
- 预连接发现(Server Card / AI Catalog)与运行时权威值(
- 考察点:
- 跨组织发布场景下的发现链路设计与信任边界划分。
- 卡片漂移的检测、对账与降级策略。
- 公开元数据的最小化与信息泄露防护。
- 发现失败的降级路径与已上线客户的不中断约束。
- 接入效率与一致性的量化验证方式。
题目
业务背景:公司决定把内部成熟的工单系统以 MCP 服务形式开放给 5 家合作伙伴的 Agent 客户端。此前每接入一家需要 2 周:人工发文档、手工配置端点、来回确认协议版本与认证方式。
系统规模:单 region 部署,3 个 MCP server(工单、知识库、报表),日均调用 40 万次,已接入 partner 客户端 8 个,目标半年内扩到 30 家。
当前架构:Streamable HTTP 传输;OAuth 2.1 授权,client_id 由运营人工发放;接入配置写在各家自己的配置文件里;没有任何机器可读的发现机制。
目标:partner 客户端只需要知道公司域名,就能自动发现可用的 MCP server 并完成连接配置,接入周期从 2 周降到 1 天以内。
限制条件:
- 目录与卡片按设计是公开内容,不得包含凭据、内网拓扑或私有端点。
- 已上线的 8 家 partner 不能中断。
- 不得改动运行时协议行为,也不得让模型参与发现与鉴权决策。
已知故障现象与风险:
- 某 partner 客户端按卡片声明的协议版本发起连接,实际 server 不支持该版本,握手失败;排查发现卡片是三个月前生成的,之后 server 升过级。
- 安全评审发现:某 partner 域下的 AI Catalog 条目被改成指向一个自建的伪造端点,卡片内容与真实 server 不一致,客户端若无防护会被引导到错误的服务。
- 一次紧急更换端点后,部分客户端在 4 小时内仍连到旧端点,因为它们缓存了卡片。
需要取舍的问题:
- 预连接发现(快、可缓存、可离线、但必然滞后)与运行时权威值(准确、但要建立连接后才能拿到)——两者如何分工?
- 卡片公开性(便于生态发现)与最小信息泄露之间的边界在哪?
- 自动发现(低接入成本)与可控性(能否防止被第三方目录条目误导)如何平衡?
先思考
- 先问一个定位问题:卡片是在连接之前读的。那么它在被读取的那一刻,内容经过任何校验了吗?
- 再问职责:连接之后,客户端还能拿到哪些信息(
server/discover、tools/list)?如果这些信息与卡片不一致,谁说了算? - 然后想缓存:
/.well-known/ai-catalog.json→ 卡片 → 客户端本地,一共有几层缓存?“发布即生效”这个假设在哪一层被打破? - 最后想防线:卡片被指向伪造端点时,能不能靠”校验卡片”来防?如果不能,真正的防线是什么?
参考答案
结论:把 Server Card 与 AI Catalog 定位为可缓存的发现提示,把 server/discover 与运行时 list 操作定位为唯一权威;卡片一律不作为安全与访问控制依据,连接建立后强制对账,冲突以运行时值为准。安全能力全部放在 TLS、OAuth 与服务端授权这些确定性机制上,模型不参与发现与鉴权决策。
问题拆解。
- 发现(where to connect):客户端只有域名时如何找到端点与传输方式。
- 能力声明(what it claims):协议版本、传输类型、身份与版本元数据。
- 信任(who may claim):谁能发布、客户端该相信谁发布的条目。
- 漂移(static vs runtime):静态清单与运行时事实之间的偏差如何检测与处置。
- 撤销(失效传播):下架或更换端点后,多久真正生效,如何度量。
核心方案。
发布侧:
- 在组织域发布 AI Catalog:
https://{domain}/.well-known/ai-catalog.json,HTTP 上应使用application/ai-catalog+json媒体类型。 - 每个条目:
identifier用域锚定 URN,格式urn:air:{publisher}:{namespace}:{name}(例如urn:air:example.com:mcp:weather);type固定为application/mcp-server-card+json;url与data二选一——url指向完整卡片,data内联整张卡片。 - 条目不重复卡片里的人类可读字段(
title/description/version),避免两份值各自漂移。 - 卡片承载位置:默认
GET <streamable-http-url>/server-card,即服务在https://host/mcp时自然得到https://host/mcp/server-card;客户端请求带Accept: application/mcp-server-card+json;服务端响应Cache-Control: public, max-age=3600与ETag,并支持If-None-Match返回304;端点必须配 CORS(Access-Control-Allow-Origin: *、GET、Content-Type/If-None-Match,暴露ETag);生产必须 HTTPS(TLS 1.2 及以上),本地开发才允许 HTTP;卡片端点应做限速。 - 卡片内容只包含:
name(反向 DNS 形式,如com.example/weather)、连接细节(传输类型与端点 URL)、元数据(title / description / version、icons)。不枚举原语——工具、资源、提示仍由运行时的 list 操作获取。
客户端侧(对齐官方发现流程):
- 取
https://{domain}/.well-known/ai-catalog.json。 - 校验返回是否是合法 AI Catalog;不合法则本次发现不可用。
- 筛出
type == application/mcp-server-card+json的条目。 - 有
url就带Accept: application/mcp-server-card+json去取卡片,有data就直接读内联卡片。 - 用卡片配置并建立连接。
- 连接后强制对账:调用
server/discover,比对serverInfo(name/version)、supportedVersions、各remotes[]端点实际提供的传输,以及描述性字段。
冲突处置(规范明确要求):
- 卡片 SHOULD 准确反映运行时行为,SHOULD NOT 与运行时观察到的等价值矛盾。
- 客户端 MUST NOT 把卡片内容当作安全或访问控制决策的权威依据。
- 客户端 SHOULD 用实时连接校验卡片声明,两者不一致时以运行时值为准。
- 工程上再加一层:连续对账冲突的 URN 降级为”仅运行时模式”(不再信任其卡片),并产生 conflict 事件进入告警。
执行流程。
- 发布流水线:server 升级 → 自动生成卡片 → 与 staging 运行时做一次对账(CI 步骤:拉卡片 + 连
server/discover比对)→ 对账通过才发布到生产目录 → 更新 ETag → 记录发布审计。 - 客户端首次接入:域名 → catalog → 卡片 → 连接 → 对账 → 缓存(catalog 与卡片各按
Cache-Control缓存,保存 ETag)。 - 客户端后续:带
If-None-Match条件请求,命中 304 就复用本地卡片;连接仍然照常建立并对账。
技术选型与依据。
- 选 AI Catalog + Server Card,而不是自建私有 registry:这是协议层的官方发现机制(SEP-2127,2026-10-06 合并为 Final,扩展标识
io.modelcontextprotocol/server-card),目录与卡片的职责已经划分清楚,且目录本身要服务 MCP 与 A2A 等多种协议,自建会多维护一套格式与生态成本。 - 默认用
url而不是data:卡片可以独立演进、独立缓存与独立失效;目录条目不重复人类可读字段,从源头减少漂移面。 - 卡片不放进
.well-known:.well-known是站点级元数据(AI Catalog 本身、OAuth 元数据如/.well-known/oauth-protected-resource属于这一类,放这里是恰当的);单个 server 的卡片是应用层元数据,且目录已经给出了它的确切url,放进.well-known不增加任何价值。 - 卡片不放在裸 streamable HTTP 端点:Streamable HTTP 传输里,对该端点的
GET已经被保留用于打开 SSE 流;把卡片也放在那里会让同一个端点承载两种语义,必须靠内容协商来区分”取卡片”和”开流”。这是规范允许但不推荐的做法。 - 备选方案及放弃理由:① 只维护文档与 SDK——放弃自动发现,接入周期降不下来;② 自建私有 registry——可控性强,但与协议发现链路不互通,partner 需要额外接入一套专有机制;③ 在卡片里枚举工具——规范明确不做,且会把”工具可见性”变成可被第三方卡片操纵的攻击面。
方案取舍。
- 发现速度 vs 准确性:卡片必须可缓存才有价值,可缓存就必然滞后。取舍是把准确性责任移交给运行时——卡片只负责”让我能连上”,连上之后由
server/discover给出事实。这正好对应现象 1 的治理:不是让卡片永不滞后(做不到),而是让滞后可被检测且不产生错误后果。 - 公开性 vs 安全性:目录与卡片按设计公开可发现,因此内容必须最小化——不写凭据、不写内网拓扑、不写私有端点、不写业务逻辑。安全属性全部由 TLS、OAuth 受众/issuer 校验与服务端授权提供,不由卡片提供。
- 自动发现 vs 可控性:客户端应维护可信发布者域白名单,只接受白名单内 URN 的目录条目。这直接对应现象 2:伪造端点的防线不是”校验卡片内容是否真实”(卡片本身就是不可信输入),而是”这个条目是否有资格被信任”加”连接时服务端是否认这个客户端”。
- 实时处理 vs 异步处理:取卡与对账都不在请求热路径上,取卡用同步短超时即可;跨 URN 的全量对账用定时异步任务做,结果进指标与告警。
失败路径、边界条件与恢复。
- catalog 返回 404 / 内容非法 → 该域名本次发现不可用,回退到文档与人工配置;已上线的 partner 不受影响,因为发现不在请求路径上。
- 卡片 404 / 超时 / ETag 变更 → 用短超时(秒级)+ 有界重试(如 2 次)+ 抖动;失败时使用上次已知配置,标注”有界陈旧”,并在对账失败时中止而非继续。
- 对账冲突(版本 / 传输 / 端点 /
serverInfo)→ 以运行时为准,记录 conflict 事件(含 URN、字段、卡片值、运行时值);同一 URN 连续冲突则降级为”仅运行时模式”。 - 卡片指向伪造端点(现象 2)→ 如果客户端做了白名单与 TLS/OAuth 校验,连接会在认证阶段失败;处置是拉黑该 URN 与发布者域,并通知 partner 其目录被篡改。
- 端点紧急更换(现象 3)→ 承认三层缓存(catalog 缓存、卡片
max-age、客户端本地),因此撤销时延 = 三层中的最大值;压缩方式是缩短max-age、发布时主动刷新 CDN、以及客户端在连接失败时强制重新取卡(绕过缓存)作为逃生通道。 - 幂等与重复:取卡是幂等 GET;客户端注册与授权用幂等键,避免重复注册产生多个 client_id。
- 恢复:保留上一版目录与卡片,支持一键回滚;卡片发布流水线保留版本历史。
安全、权限、成本、延迟与可靠性。
- 安全:公开内容最小化;URN 发布者域白名单;强制 HTTPS;OAuth 受众绑定与 issuer 校验;服务端对每个 partner 做授权判定(卡片不参与);审计记录”谁发现了什么、谁连上了、冲突了几次”。
- 权限:卡片只解决”连哪里”,不解决”能用什么”;工具可见性与可调用性由服务端按 partner 身份过滤后经运行时 list 返回。
- 成本:目录与卡片是低频读,加 CDN 与 ETag 后成本可忽略;真实成本在对账任务与观测。
- 延迟:发现阶段增加约 2 次 RTT(catalog + card),有缓存与 304 后接近 0;不进入请求热路径。
- 可靠性:把卡片端点设计为”可降级的旁路”——它挂了不影响已有连接,只影响新接入。这是承接”已上线 partner 不能中断”约束的关键设计。
三层能力归属。
- 模型的概率性能力:不参与。发现、鉴权、对账判定都不能交给模型——模型可能把”卡片说支持 v2”当成”实际支持 v2”,而这正是现象 1 的成因。
- Agent 编排能力:执行发现流程(取 catalog → 选条目 → 取卡 → 连接 → 对账)、ETag 缓存复用、冲突降级策略、有界重试与退避、失败时的降级路径选择。
- 传统软件系统的确定性能力:HTTPS/TLS 与证书校验、CORS、限速、ETag 与
Cache-Control、URN 解析与白名单、OAuth 鉴权与受众校验、服务端授权判定、卡片生成与发布流水线、定时对账任务、审计日志与指标。
评测指标、测试方法与上线后观测。
- 指标:发现成功率(按 partner)、catalog 命中率、卡片请求 304 比例、卡片→运行时一致率、对账冲突率(按 URN 与字段)、按 partner 的连接失败率与错误分类、平均接入耗时(目标 2 周 → 1 天)、撤销生效时延(发布到客户端实际切换的 P99)。
- 测试:CI 里加”发布后自动对账”门禁;构造四类卡片异常(版本滞后、端点错误、缺失字段、指向伪造端点)验证客户端降级与告警;做一次真实的新 partner 接入演练计时。
- 上线后:按 URN 看板监控冲突率;冲突率突增通常意味着发布流水线漏了对账步骤;对”仅运行时模式”的 URN 数量设阈值告警。
面试官追问一:卡片和 server/discover 都声明了版本,为什么需要两份?只维护一份不行吗?
回答要点:不行,因为两者的使用场景不同且不可互相替代。卡片在连接之前被读取,那时还没有任何协议交互,客户端需要它来决定”用什么配置去连”;server/discover 在连接之后返回,是运行时的权威事实。要求”只有一份”等于要求静态清单永不滞后,而这在有缓存、有多版本部署、有灰度发布的现实里做不到。正确的工程姿态是把两者的差值本身当作一个可观测对象——也就是漂移率,用对账来治理,而不是试图消除它。
面试官追问二:如果卡片被 partner 域的目录条目指向伪造端点,防线在哪一层?
回答要点:不在卡片层。规范明确规定客户端 MUST NOT 把卡片内容当作安全或访问控制决策的权威依据,原因是卡片在连接前读取、内容未经校验,本质上是不可信输入。真正的防线是三层确定性机制:① 客户端只信任白名单发布者域的 URN,非白名单条目直接拒绝;② 连接时的 TLS 证书校验与 OAuth 受众/issuer 校验——伪造端点拿不到合法证书与合法受众声明;③ 服务端对每个 partner 的授权判定,即使连上了也只暴露该 partner 被授权的工具。此外要有检测与响应:对账冲突事件 + 拉黑机制。只做”卡片内容校验”是方向性错误,因为它把安全建立在了一份可被任意第三方发布的文件上。
面试官追问三:为什么规范明确不让卡片枚举工具、资源与提示?如果产品上很想在目录里展示”这个 server 有哪些工具”怎么办?
回答要点:三个理由。第一,静态清单必然漂移,工具集会随版本与授权变化,声明出来的那一刻就开始过期。第二,工具可见性是授权结果而不是服务的静态属性——不同 partner 看到的工具集本来就不同,静态枚举要么泄露要么失真。第三,如果客户端依据卡片决定”有哪些工具可用”,那么能控制卡片的人就间接控制了客户端的工具视图,这是一个明确的攻击面。产品上要展示工具,正确做法是在已建立连接之后用运行时 list 结果渲染,或在目录里只放人工撰写的概述性描述并明确标注”以连接后实际列表为准”;绝不能让静态枚举进入调用决策路径。
小型实践(30–60 分钟):
- 用任意静态服务起两个端点:
/.well-known/ai-catalog.json(一条urn:air:...条目,type为application/mcp-server-card+json,url指向卡片)和/mcp/server-card(返回带name、连接细节、version的 JSON,配Cache-Control: public, max-age=3600与ETag)。 - 写一个客户端脚本完成四步发现流程,并打印每一步的耗时与状态码;再加第 5 步:带
If-None-Match重取,确认返回 304。 - 写一个对账函数:把卡片里的
version/ 传输类型 / 端点与一个”模拟server/discover”返回值做比对,输出冲突报告(字段、卡片值、运行时值、处置动作)。 - 故意把卡片的
version改错,跑一遍,确认对账能捕获并触发”以运行时为准”的分支;再统计一次”撤销生效时延”——改卡片后多久客户端才拿到新值。
关键点
- 卡片是提示,运行时是权威:卡片服务于”连接前如何配置”,
server/discover与 list 服务于”连接后事实是什么”;冲突一律以运行时为准。 - 卡片 MUST NOT 作为安全或访问控制依据:它在连接前读取、内容未校验,是可被第三方发布的不可信输入;安全由 TLS、OAuth 与服务端授权承担。
- 卡片不枚举原语:工具、资源、提示由运行时 list 决定,且可见性本身是授权结果。
- 承载位置是应用层而非站点级:默认
<streamable-http-url>/server-card;.well-known只适用于 AI Catalog 与 OAuth 这类站点级元数据;裸 streamable HTTP 端点的GET已被 SSE 流占用。 - 撤销存在三层缓存时延:catalog 缓存、卡片
max-age、客户端本地缓存;“发布即生效”不成立,必须度量撤销生效时延并准备强制重取的逃生通道。
常见误区
- 把 Server Card 当服务目录或权限来源。错误:规范明确禁止把卡片内容用于安全或访问控制决策;卡片是连接前读取的未校验输入,且公开可被第三方发布。正确做法是一切授权判定放在服务端,卡片只提供连接配置。
- 把卡片放在
/.well-known/mcp/server-card。错误:.well-known用于站点级元数据(AI Catalog 本身、OAuth 元数据适用);单个 server 的卡片是应用层元数据,且目录条目已经给出了确切url,放进.well-known不带来任何收益。默认位置是GET <streamable-http-url>/server-card。 - 认为发布即生效,或认为卡片永不滞后。错误:三层缓存决定撤销有可观测的时延;静态清单在有灰度与多版本部署的环境里必然滞后。正确做法是承认滞后、量化它、并用连接后对账让滞后不产生错误后果。
延伸学习
- SEP-2127《MCP Server Cards - HTTP Server Discovery》:https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127(已核验:由 dsp-ant 于 2026-10-06 合并进 main;摘要说明该机制让客户端在建立连接前发现 server capabilities、available transports 与 protocol versions;
.well-known/ai-catalog.json用于预连接发现) - MCP Server Card 官方发现文档:
experimental-ext-server-card仓库docs/discovery.md(已核验原文:URN 格式urn:air:{publisher}:{namespace}:{name};条目url/data二选一;“Server Cards do not enumerate primitives”;“Clients MUST NOT treat Server Card contents as authoritative for security or access-control decisions”;“Clients SHOULD verify a Server Card’s claims against the live connection, preferring the runtime values where the two disagree”;默认位置GET <streamable-http-url>/server-card,并给出.well-known与裸端点两种方案的不推荐理由;CORS /Cache-Control: public, max-age=3600/ETag/ TLS 1.2+ / 限速要求) - 未核验:本轮未抓取
server/discover的规范条文细节,该文档自身标注 Protocol Revision 为 draft,引用时以官方当前版本为准。
S02 · 非 AI Agent 真实研发场景题
- 题目 ID:
Q-20261020-S02 - 一级类型:操作系统与网络
- 二级主题:mTLS 证书轮换、TLS 会话恢复与长连接的信任有效期
- 核心考点:
- TLS 1.3 会话恢复不重新进行证书认证,会话寿命才是真正的安全边界。
- 吊销生效时延由会话票据寿命与连接最大存活共同决定,与证书剩余寿命是两个指标。
- 轮换窗口的握手风暴是自造的惊群,需要错峰与并发上限控制。
- 握手失败必须按原因分类,鉴权类失败不可重试。
- 考察点:
- 长连接场景下证书状态重新被检查的机制设计。
- 安全(吊销生效速度)与延迟 / CPU(会话复用)之间的量化权衡。
- 失败分类、重试预算与幂等边界。
- 轮换与回滚的可操作性设计。
- 可验证的观测指标与压测方法。
题目
业务背景:公司内部服务网格,220 个服务之间全部强制 mTLS 互访,客户端证书由内部 CA 签发,当前有效期 90 天,由 sidecar 自动续期。
系统规模:峰值 12 万 QPS;网格内长连接约 40 万条;服务间 P99 延迟要求低于 120 ms;握手 CPU 预算占总 CPU 的 5% 以内。
当前架构:服务间 HTTP/2 长连接 + 连接池,单条连接平均存活 6 小时;启用 TLS 会话票据(session ticket)复用;0-RTT 早期数据在内网链路默认开启;证书续期脚本在证书剩余 30 天时触发,重启时热加载。
目标:在不突破 P99 与 CPU 预算的前提下,让”证书轮换”和”证书吊销”两件事具备可预期的生效时间,并给出可验证的 SLO。
限制条件:
- 不能全量强制重连,否则握手 CPU 会击穿预算。
- 不能给业务请求引入额外 RTT。
- 证书有效期将随行业规则继续缩短,方案必须适应更短的证书。
- 不允许降低已有的身份验证强度。
已知故障现象:
- 一次证书轮换完成后,监控发现仍有约 7% 的长连接在之后的 6 小时内一直使用旧会话通信,没有重新握手。
- 一次安全事件中紧急吊销了某个客户端证书,事后审计显示该客户端在吊销后约 40 分钟内仍能成功调用写接口。
- 每次轮换窗口,握手失败率出现尖峰,同时网格整体 CPU 上升约 20%,持续数分钟。
需要取舍的问题:
- 会话复用(省一次 RTT 与非对称运算,低延迟低 CPU)与证书/吊销生效速度(安全)之间如何权衡?
- 吊销检查用 fail-open 还是 fail-closed?不同链路是否应该有不同策略?
- 是”吊销时立刻全量断连”,还是”平时就给连接设寿命上限”?
先思考
- 先想一个反直觉的问题:一条已经建立的长连接,之后每一次请求还会校验证书吗?如果不校验,那”证书有效期”在这条连接上意味着什么?
- 再想现象 2:吊销生效了 40 分钟,这 40 分钟是由什么决定的?是 CA 的 CRL 更新频率,还是别的什么?
- 然后想现象 3:为什么轮换会造出一个尖峰?谁在同一时刻做了同一件事?
- 最后想成本:如果把连接寿命从 6 小时压到 30 分钟,握手量会涨多少?这笔账应该怎么算、怎么摊?
参考答案
结论:TLS 1.3 的会话恢复把”证书有效期”悄悄替换成了”会话有效期”——恢复握手不发送也不校验证书,因此在这条连接上,证书是否被吊销根本没有被检查。解决路径不是”吊销时全量断连”,而是给连接与会话票据显式设置寿命上限,让证书状态被周期性重新检查,同时用错峰续期与并发上限把成本摊平。
问题拆解。
- 现象 1(旧会话继续通信):TLS 1.3 中,服务端通过 PSK 恢复会话时,不再发送
Certificate与CertificateVerify消息,因此不做证书认证。RFC 8446 §2.2 的原文是:由于服务端通过 PSK 完成认证,它不发送 Certificate 或 CertificateVerify 消息;§4.1.1 进一步明确,在本文档定义的 TLS 1.3 中,PSK 与证书总是二选一使用,不会同时使用。也就是说,一次成功的恢复握手里,证书状态从未被访问。连接不重建,证书就永远不被重新检查。 - 现象 2(吊销 40 分钟才生效):同上,恢复握手让吊销检查机制(CRL / OCSP)根本没有执行机会。因此吊销的实际生效时延约等于
max(会话票据寿命, 连接最大存活),而不是 CA 的吊销发布时延。40 分钟这个数字,本质上是”票据寿命”和”连接平均/最大存活”的函数。 - 现象 3(轮换尖峰):续期脚本在固定剩余天数触发、且大量实例的证书签发时间高度接近,导致海量连接在几乎同一时刻需要重建。握手是非对称运算密集的操作,集中触发就是一次自造的惊群;失败率尖峰则来自新旧信任包短暂不一致期间的部分握手失败。
核心方案。
- 给连接设寿命上限:配置
max_connection_lifetime(例如 30–60 分钟)与max_requests_per_connection,到期用GOAWAY优雅关闭并重建。这是让”证书状态重新被检查”的唯一确定性机制——不重建连接,就没有检查。 - 给会话票据设寿命上限:
session_ticket_lifetime不超过吊销生效时间目标(例如 15 分钟)。票据寿命直接决定了”吊销后还能通信多久”的下界。 - 关闭 0-RTT 或严格限制:0-RTT 早期数据同样不经过证书认证,且存在重放风险;只有天然幂等的请求才能承载。对内网写接口,默认关闭是更安全的默认值。
- 吊销检查策略:优先使用 OCSP stapling(由服务端在握手中携带响应),避免客户端额外出网依赖;按链路风险分级决定 fail-open / fail-closed——高风险写链路 fail-closed,其余 fail-open 并产生审计告警。
- 轮换节奏:在证书寿命约 2/3 处续期;续期时间加随机抖动打散;客户端与服务端的新旧证书/信任包保留重叠窗口,确保任一侧先切都不会握手失败。
- 抗惊群:限制同时重建连接的比例(令牌桶或并发上限),配合
GOAWAY优雅排空,把重建压力摊到一个时间窗里。
执行流程。
- 平时:连接按
max_connection_lifetime或请求数上限到期 →GOAWAY→ 新连接在后台预热 → 切换 → 旧连接排空后关闭。 - 续期:证书寿命到 2/3(含抖动)→ 内部 CA 签发新证书 → sidecar 热加载(新旧并存窗口)→ 验证新证书可用后才移除旧证书 → 记录续期审计。
- 吊销:CA 标记吊销 → 缩短全局
session_ticket_lifetime(或直接下发”强制重建”指令,按批次限速)→ 服务端在完整握手时拒绝被吊销身份 → 对高风险链路立即 fail-closed。 - 回滚:保留上一版证书与信任包,提供一键回滚信任锚的运维能力。
技术选型与依据。
- 选”连接寿命上限 + 票据寿命上限 + 错峰续期”,而不是”吊销时全量断连”:前者是恒定的、可预算的低成本(握手量按恒定速率增加),后者是偶发的高幅冲击(一次吊销可能打满 CPU 并引发可用性事故)。可预期的成本优于不可预期的故障。
- 选 OCSP stapling,而不是客户端直连 OCSP 响应器:不增加客户端 RTT、不把 OCSP 响应器变成握手路径上的新可用性依赖、也不泄露客户端的访问模式。
- 选”错峰 + 并发上限”,而不是”缩短周期让重建更频繁”:尖峰的根因是相关性(correlated),不是总量;消除相关性用抖动与限速,而不是继续加总量。
- 备选方案:超短证书 + 强制每次完整握手(安全性最强,但 CPU 与延迟成本显著,只适合小规模高风险链路);或改用长期会话 + 服务端主动推送吊销名单(可用性最好,但吊销生效时延不可控,不满足目标)。
方案取舍。
- 安全 vs 延迟 / CPU:会话复用省掉一次 RTT 与两次非对称运算;缩短会话寿命就是花钱买安全。量化方式:若把平均连接寿命从 6 小时压到 30 分钟,握手 QPS 约上升 12 倍,必须用错峰、批量与票据缓存摊平,否则 CPU 预算会被击穿。
- 吊销即时性 vs 可用性:fail-closed 在 OCSP 响应器不可用时直接断连,可能把单点故障放大为全网格故障;建议按链路风险分级,高风险写链路 fail-closed,其余 fail-open + 审计告警 + 限时使用。
- 自动化 vs 可控性:证书越短越依赖自动化。行业趋势明确——Let’s Encrypt 当前默认签发 90 天证书,另有 6 天短期证书可选;计划 2027-02-10 将默认 profile 转为 64 天(授权复用期 10 天),2028-02-16 进一步转为 45 天(授权复用期 7 小时);行业规则将于 2029-03-15 起把有效期上限压到 47 天。官方同时指出:硬编码 60 天续期间隔将不再足够,建议按证书寿命的约 2/3 续期,并使用 ACME Renewal Information(ARI)判断续期时机。这意味着手动续期与固定间隔脚本都会被淘汰。
- 实时 vs 异步:续期与吊销传播走异步控制面;数据面的重连必须限速异步进行,绝不能同步阻塞业务请求。
失败路径、边界条件、超时、重试、幂等与恢复。
- 续期失败:保留旧证书直到真正过期;按剩余寿命分级告警(低于 1/3 告警,低于 1/7 严重);绝不在续期失败时立刻切到未验证的新证书。
- 握手失败分类(关键):证书过期、主机名不匹配、未知 CA、证书被吊销 → 不可重试,重试只会放大无效流量并把安全事件伪装成性能问题;网络超时、连接被拒、服务端 5xx → 可重试,带退避与抖动,且重试预算有上限。
- 连接池行为:收到
GOAWAY的连接不再接受新请求,正在飞行的请求允许完成或按 deadline 取消;新请求走新连接;预热连接要在旧连接排空之前就绪。 - 时钟与边界:证书校验依赖时钟,容器时钟漂移会直接导致误判;需要 NTP 与时钟偏移监控。证书重叠窗口要大于最坏情况下的传播时延。
- 幂等:证书轮换本身无业务副作用;但连接重建引发的请求重试必须携带幂等键,否则”连接层重试”会被放大成”业务重复写”。
- 恢复:提供限速分批的”强制全量重连”运维开关;保留上一版信任包支持一键回滚。
性能、可用性与验证指标。
- 指标:握手 P50 / P99 耗时与握手失败率(按错误原因分类)、会话恢复率、连接实际寿命分布(P50 / P99)、全网格证书剩余寿命最小值、吊销生效时延(从 CA 标记吊销到该身份请求被拒绝的 P99)。
- 压测:模拟一次完整轮换窗口,验证握手 CPU 峰值是否在 5% 预算内、P99 是否低于 120 ms、失败率是否低于阈值;再做一次吊销演练,实测吊销生效时延是否达标。
- 告警:证书剩余寿命分级告警;任何身份在吊销后超过目标时延仍能成功调用 = 严重告警(这是最直接的安全 SLO);握手失败率按原因分类突变告警;会话恢复率突降(可能意味着票据配置被误改)。
- 上线后观测:把”连接寿命分布”做成常驻看板,因为它是安全与成本二者的共同控制变量。
面试官追问一:TLS 1.3 的会话恢复握手为什么不重新校验证书?这样设计合理吗?
回答要点:机制上,服务端通过 PSK 恢复时不再发送 Certificate 与 CertificateVerify(RFC 8446 §2.2 原文),且规范明确 PSK 与证书在 TLS 1.3 中二选一、不同时使用(§4.1.1);认证由 Finished 消息的密钥确认完成——持有相同 PSK 本身就证明了双方仍是之前握手的那一对,规范把 PSK 认证描述为密钥交换的副作用。设计上这是合理的性能取舍:省掉一次 RTT 与两次非对称运算,对大规模服务网格是决定性收益。但它有一个必须被显式管理的推论:会话寿命成为了一个新的、隐式的身份有效期。工程上不能沿用默认配置就完事,必须把票据寿命与连接寿命显式设置到与吊销 SLO 匹配的水平。
面试官追问二:证书有效期被压到 45 天以后,系统要改什么?只改续期脚本够吗?
回答要点:不够,而且只改脚本会踩坑。三处必须同时改:① 续期策略——硬编码的固定间隔(如每 60 天)会直接失效,官方明确提示这种做法不再足够,应改为按寿命约 2/3 续期,并优先使用 ARI 之类的续期信息判断时机;② 会话与连接寿命——缩短证书的收益会被长会话完全抵消,如果连接还是存活 6 小时、票据还是能用 12 小时,那么 45 天证书和 90 天证书在这条连接上没有区别;③ 自动化与告警——更短的证书意味着续期失败的可容忍窗口也变短,需要有剩余寿命的分级告警与失败后的自动重试与人工升级路径。此外还要重新评估握手 CPU 预算,因为续期更频繁通常伴随重连更频繁。
面试官追问三:0-RTT 要不要开?开了能省一个 RTT,为什么很多团队默认关?
回答要点:0-RTT 同样不经过证书认证,且早期数据在完整握手确认之前就被发送,存在被重放的可能。因此 0-RTT 只适合承载天然幂等、且重放不产生副作用的请求(例如部分只读查询);对写接口、扣款、下单这类请求必须关闭。多数团队默认关闭的理由是:一个 RTT 的收益(内网通常是亚毫秒级)远小于一次重放事故的成本,而这个风险还很难在监控上被发现。如果要开,必须配套:请求级幂等键、重放窗口限制、以及只在明确标注幂等的接口上启用。
小型实践(30–60 分钟):
- 用 OpenSSL 自建一个 mini CA,签发服务端与客户端证书,起一个强制 mTLS 的 HTTPS 服务。
- 用
openssl s_client分别做一次完整握手和一次会话恢复(-sess_out/-sess_in),对比两次握手的输出,确认恢复握手没有 Certificate 消息。 - 吊销客户端证书后,在不重连的情况下继续发请求,观察是否能继续成功;记录从吊销到实际被拒的时间,这就是吊销生效时延的雏形。
- 给客户端加上”连接最大存活 30 秒”的限制重跑一次,观察吊销生效时延的变化,并统计握手次数增加了多少。
关键点
- 会话恢复不校验证书:TLS 1.3 中 PSK 恢复不发送
Certificate/CertificateVerify,证书状态在这条连接上从未被检查;不重建连接就等于永久信任旧证书。 - 吊销生效时延 ≠ 证书剩余寿命:它由
max(票据寿命, 连接最大存活)决定,必须作为独立指标度量;只监控证书剩余寿命防过期、不防吊销。 - 握手失败必须分类:证书/吊销/鉴权类失败不可重试;网络与 5xx 类可重试,且要有退避与重试预算。
- 尖峰的根因是相关性:大量实例在同一时刻续期与重连造成自造惊群,治理手段是抖动与并发上限,而不是继续缩短周期。
- 证书越短,自动化要求越高:硬编码续期间隔会失效,需改为按寿命比例续期并依赖续期信息服务;同时会话与连接寿命必须同比压缩,否则收益被长会话抵消。
常见误区
- 认为吊销后连接立即失效。错误:恢复握手不校验证书,实际生效时延由会话票据寿命与连接最大存活决定;本例的 40 分钟正是这两个参数的函数,而不是 CA 的处理速度。正确做法是显式设置票据寿命与连接寿命,并把吊销生效时延做成可观测指标与演练科目。
- 只对”证书剩余寿命”做告警。错误:这能防过期(可用性问题),防不住吊销(安全问题);两者是不同的失效模式、不同的指标、不同的处置流程。正确做法是分别建立”过期预警”与”吊销生效时延”两套 SLO。
- 对 TLS 握手失败做无差别重试。错误:证书过期、未知 CA、吊销、主机名不匹配都是确定性的安全判定,重试一万次结果相同,只会放大无效流量、抬高 CPU,并把安全事件伪装成性能抖动。正确做法是按错误原因分类,鉴权类直接失败并告警。
延伸学习
- RFC 8446《The Transport Layer Security (TLS) Protocol Version 1.3》§2.2、§4.1.1:https://datatracker.ietf.org/doc/html/rfc8446(已核验原文:“As the server is authenticating via a PSK, it does not send a Certificate or a CertificateVerify message.”;“In TLS 1.3 as defined by this document, either a PSK or a certificate is always used, but not both.”;“PSK-based authentication happens as a side effect of key exchange.”。注意:本次抓取内容在 §4.2.7 之后截断,§4.4.2 Certificate 一节未取到,证书有效期校验的具体条文不在核验范围内)
- Let’s Encrypt《Decreasing Certificate Lifetimes to 45 Days》(2025-12-02)与《Certificate Lifetime Rationale and Plans》:https://letsencrypt.org/2025/12/02/from-90-to-45 、https://letsencrypt.org/docs/cert-lifetimes(已核验:当前默认 90 天、另有 6 天短期证书;2027-02-10 默认 profile 转 64 天 + 10 天授权复用期;2028-02-16 转 45 天 + 7 小时;行业规则 2029-03-15 起上限 47 天;原文提示 “renewing at a hardcoded interval of 60 days will no longer be sufficient”,建议按证书寿命约 2/3 续期并使用 ARI)
L01 · LeetCode
- 题目 ID:
Q-20261020-L01 - 一级类型:链表 / 快慢指针(Floyd 判圈)
- 难度:medium
- 主要考察的数据结构或算法模式:链表的双指针;Floyd 判圈算法(环形检测 + 入环点定位);O(1) 额外空间约束下的数学推导。
题目
给定一个链表的头节点 head,返回链表开始入环的第一个节点。如果链表无环,返回 null。
补充说明:评测系统内部用整数 pos 表示链表尾连接到链表中的位置(索引从 0 开始),pos = -1 表示无环,但 pos 不会作为参数传入。不允许修改链表。
进阶要求:能否用 O(1) 空间解决?
先思考
- 提示 1:如果链表中存在环,一个每次走一步的指针和一个每次走两步的指针从同一起点出发,它们会不会相遇?如果会,为什么?如果不会(走到了
null),能推出什么? - 提示 2:假设两者在环内某点第一次相遇。设「链表头到入环点」距离为
a,「入环点到相遇点」距离为b,「相遇点再走回入环点」的距离为c(即环长为b + c)。用”快指针路程是慢指针两倍”这个关系,能推出a与c之间什么等式? - 提示 3:如果能推出
a = c(或a ≡ c mod 环长),那么把其中一个指针放回链表头、另一个留在相遇点,两者同速前进,会在哪里相遇?
参考答案
题号:142
中文名:环形链表 II
英文名:Linked List Cycle II
难度:Medium
官方链接:
- 中文:https://leetcode.cn/problems/linked-list-cycle-ii/(已核验)
- 英文:https://leetcode.com/problems/linked-list-cycle-ii/(已核验;官方题目列表同时标注为 Med;英文页显示通过率约 59%,该数值会随时间变化)
题目描述:给定链表头节点,若存在环则返回入环的第一个节点,否则返回 null。不允许修改链表。
输入 / 输出:输入为单链表头节点 head(节点数 0 到 10^4);输出为入环节点或 null。
约束:
- 链表中节点数目范围
[0, 10^4] -10^5 <= Node.val <= 10^5pos为-1或链表中的一个有效索引(pos不传入函数)
示例:
| # | 输入 | 输出 | 解释 |
|---|---|---|---|
| 1 | head = [3,2,0,-4], pos = 1 | 返回索引为 1 的节点(值为 2) | 链表尾连接到第二个节点 |
| 2 | head = [1,2], pos = 0 | 返回索引为 0 的节点(值为 1) | 链表尾连接到第一个节点 |
| 3 | head = [1], pos = -1 | null | 链表无环 |
直观方案及其局限:用一个哈希集合记录访问过的节点,遍历时若遇到已访问的节点即为入环点。时间与空间复杂度均为 O(n)。它的局限很明确——不满足题目进阶要求的 O(1) 空间,而且在节点数上万、或需要常驻内存做循环检测的场景(如运行时链路检测)里,O(n) 的额外内存本身就是负担。
推荐方案的推导(Floyd 判圈,两阶段):
设慢指针 slow 每次走 1 步,快指针 fast 每次走 2 步,二者从头节点同时出发。
阶段一:判环。
- 若无环,
fast会先走到null(fast或fast.next为空),循环结束,返回null。 - 若有环,
fast一定先进入环并在环内绕圈;slow进入环后,fast相对slow每步逼近 1 个节点,因此必然在slow走完第一圈之前追上它——两者必定在环内相遇。这一点是存在性的保证:相对速度差为 1,不存在”错过”的可能。
阶段二:定位入环点。
记「头 → 入环点」距离为 a,「入环点 → 相遇点」为 b,「相遇点 → 回入环点」为 c,环长 L = b + c。相遇时:
slow路程:a + bfast路程:a + b + k·L(k ≥ 1,快指针在环内多绕了k圈)- 速度关系:
2(a + b) = a + b + k·L
整理得 a + b = k·L,即 a + b 是环长的整数倍。于是:
a = k·L − b = (k − 1)·L + (L − b) = (k − 1)·L + c即 a ≡ c (mod L)。这个等式的物理含义是:从头节点走 a 步 与 从相遇点走 c(或 a)步,落在同一个节点——入环点。
因此第二阶段的做法是:把一个指针放回 head,另一个留在相遇点,两者都改为每次走一步,它们第一次相遇的位置就是入环点。
正确性说明:
- 阶段一不会错过:有环时两者都在环上,相对速度为 1,距离每步严格减 1,必在某步变为 0,即相遇;因此”相遇”是”有环”的充分必要条件(无环时
fast必然先到null)。 - 阶段二相遇点即入环点:由上面的等式
a ≡ c (mod L),头节点出发的指针走a步到达入环点;相遇点出发的指针走a步等价于走c步(模环长意义下),同样到达入环点。两者步数相同、终点相同,故首次重合处就是入环点。 - 不修改链表:全过程只移动指针,符合题目约束。
时间复杂度:O(n)。阶段一快指针最多走约 2n 步、慢指针最多 n 步;阶段二至多走 a ≤ n 步。
空间复杂度:O(1),只使用两个指针变量。
可运行的 Python 参考代码(以下代码已在本机执行,10 个测试用例全部通过):
from typing import Optional
class ListNode: def __init__(self, x: int): self.val = x self.next = None
class Solution: def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: slow = fast = head
# 阶段一:判断是否有环 while fast is not None and fast.next is not None: slow = slow.next fast = fast.next.next if slow is fast: # 相遇即存在环 # 阶段二:定位入环点 p = head while p is not slow: # 同速前进,首次重合处即入环点 p = p.next slow = slow.next return p
return None # fast 走到 null,无环关键边界情况:
- 空链表(节点数 0):
head为None,while条件直接不成立,返回None。 - 单节点自环(
[1],pos = 0):第一次迭代后slow与fast都回到自身,立即相遇;第二阶段p与slow已重合,直接返回该节点。 - 尾节点自环 / 环长为 1(如
[1,2,3,4,5],pos = 4):a ≡ c (mod 1)恒成立,推导依然有效。 - 入环点就是头节点(
pos = 0):此时a = 0,第二阶段两个指针起点已重合,循环不执行,直接返回head。 - 循环条件必须同时判
fast与fast.next:只判fast会漏掉”快指针正好停在最后一个节点”的情况,fast.next.next会抛空指针错误。 - 比较用
is而非==:题目要返回节点对象本身;若链表节点自定义了==,值比较会导致误判(两个不同节点值相同)。
测试用例(下列前 3 个为官方示例,其余为补充边界,均已实际运行通过):
| # | 输入 | 预期输出 |
|---|---|---|
| 1 | head = [3,2,0,-4], pos = 1 | 值为 2 的节点(索引 1) |
| 2 | head = [1,2], pos = 0 | 值为 1 的节点(索引 0) |
| 3 | head = [1], pos = -1 | null |
| 4 | head = [](空链表) | null |
| 5 | head = [1], pos = 0(单节点自环) | 值为 1 的节点 |
| 6 | head = [1,2,3], pos = -1(无环) | null |
| 7 | head = [1,2,3,4,5], pos = 2(环入口在中部) | 值为 3 的节点(索引 2) |
| 8 | head = [1,2,3,4,5], pos = 4(尾节点自环,环长 1) | 值为 5 的节点(索引 4) |
面试官可能提出的变体或追问:如果链表非常长(百万级节点)且内存受限,只能在环外访问一次、不能回退到头节点,还能定位入环点吗?
回答要点:可以,但要放弃 O(1) 空间或放弃”单次遍历”。思路是:相遇后,从相遇点继续走一圈即可测出环长 L;然后让两个指针都从头出发,其中一个先走 L 步,之后同速前进,首次重合处即入环点(本质是求”倒数第 L 个节点”)。这仍是 O(1) 空间、O(n) 时间,但需要第二次从头遍历——这说明题目允许”回到头节点”这一条件在 O(1) 空间解里是被实际使用的。另一种思路是不求入环点、只求环长(相遇后固定一个指针、另一个继续走并计数直到再次相遇),这在很多工程场景里已经够用。
与 AI Agent 工程的关系:Floyd 判圈的结构可以直接迁移到 Agent 的死循环检测。把 Agent 轨迹中的 (工具名, 规范化参数) 序列视为一条链:用一个”慢快照”与一个”快快照”(或状态指纹集合)做周期性比对,若两个快照的状态指纹重合,说明 Agent 在重复路径上打转,应立即触发中断与降级(例如切换到确定性工作流或转人工)。这里的关键工程点同样是判据必须是状态指纹而非表面文本——两次调用参数略有不同(时间戳、序号变化)时,文本比较会漏判,而规范化后的指纹才能捕获真正的循环。
关键点
- 相遇即充分必要条件:有环时相对速度为 1,必然相遇;无环时
fast必然先到null。不存在第三种情况。 - 核心等式是
a ≡ c (mod L),由2(a+b) = a+b+k·L推出;它是”从头走a步”与”从相遇点走a步”落在同一点的根据。 - 第二阶段必须同速:只有把速度改成一致,才会首次重合在入环点;保持 1:2 会继续在环内绕圈。
- 循环条件要同时判
fast与fast.next,否则会漏掉快指针停在尾节点的情况。 - 可迁移能力:把”检测结构中的重复/回路”抽象为”两个不同速率的观察者 + 状态指纹”,这一模式可复用到链表环检测、数组重复数检测(快慢下标)、以及运行时的循环与重复状态检测。
常见误区
- 用哈希集合就交卷。错误:空间复杂度 O(n),不满足题目的 O(1) 进阶要求;在需要常驻内存的检测场景里,额外内存本身就是成本。正确做法是 Floyd 判圈。
- 相遇后直接返回相遇点。错误:相遇点通常在环内部,不是入环点;必须经过第二阶段从头节点同速前进才能定位入环点。
- 循环条件只写
while fast。错误:fast.next可能为None,fast.next.next会抛空指针异常;必须写成while fast is not None and fast.next is not None。
延伸学习
- LeetCode 官方题目页(中英文均已核验,题号、标题、Medium 难度、三个示例与三条约束完全一致):https://leetcode.com/problems/linked-list-cycle-ii/ 、https://leetcode.cn/problems/linked-list-cycle-ii/
- 实践方向:把本题的”快慢观察者 + 状态指纹”模式迁移到自己项目的循环检测里,例如在 Agent 轨迹上做
(tool, normalized_args)指纹的重复检测,并验证它对”参数微变但语义相同”的调用是否有效。
延伸资料
按官方优先于二手的原则,本轮实际核验过的来源如下。
| 主题 | 资料 | 核验状态 |
|---|---|---|
| B01 异常链 | Python 官方教程《8. Errors and Exceptions》§8.5 / §8.10 https://docs.python.org/3/tutorial/errors.html | 已核验:from 子句、__cause__ / __context__ 两条回溯提示语原文、raise ... from None、add_note 与 ExceptionGroup 示例 |
| B02 项目级约定 | AGENTS.md 官方站点 https://agents.md/ | 已核验:定位原文 “Think of AGENTS.md as a README for agents”;与 README 分开的三条理由;嵌套文件就近优先原文;页面称已被 6 万+ 开源项目采用 |
| S01 Server Card | SEP-2127 PR #2127 https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127 | 已核验:2026-10-06 由 dsp-ant 合并进 main;摘要说明连接前发现 capabilities / transports / protocol versions;.well-known/ai-catalog.json 用于预连接发现 |
| S01 发现流程 | experimental-ext-server-card 仓库 docs/discovery.md | 已核验原文:URN urn:air:{publisher}:{namespace}:{name};url / data 二选一;卡片不枚举 primitives;“Clients MUST NOT treat Server Card contents as authoritative for security or access-control decisions”;冲突以运行时值为准;默认位置 <streamable-http-url>/server-card 及 .well-known / 裸端点的不推荐理由;CORS、Cache-Control: public, max-age=3600、ETag、TLS 1.2+、限速 |
| S02 TLS 会话恢复 | RFC 8446 §2.2 / §4.1.1 https://datatracker.ietf.org/doc/html/rfc8446 | 已核验原文:“As the server is authenticating via a PSK, it does not send a Certificate or a CertificateVerify message.”;“either a PSK or a certificate is always used, but not both.”。限制:抓取内容在 §4.2.7 后截断,§4.4.2 Certificate 一节未取到,证书有效期校验条文不在核验范围 |
| S02 证书有效期 | Let’s Encrypt《Decreasing Certificate Lifetimes to 45 Days》(2025-12-02) 与《Certificate Lifetime Rationale and Plans》 | 已核验:当前默认 90 天、6 天短期证书可选;2027-02-10 默认 profile 转 64 天 + 10 天授权复用;2028-02-16 转 45 天 + 7 小时;行业规则 2029-03-15 起上限 47 天;原文提示硬编码 60 天续期间隔不再足够,建议按寿命约 2/3 续期并使用 ARI |
| L01 | LeetCode 142 中英文官方页 | 已核验:题号、标题、Medium 难度、三个示例、三条约束两岸一致;英文页通过率约 59%(随时间变化) |
未核验项(不得当事实引用):server/discover 的规范条文细节(来源文档自标 Protocol Revision 为 draft);Agent Skills 规范的具体数值阈值(沿用 2026-10-14 已核验记录,本轮未重新抓取);B01 中涉及的各语言错误模型对比(Go error、Java 检查型异常)未做官方原文核验,仅作为概念对比使用。
当日重点
- 失败不能被降级为数据(B01):返回默认值的静默失败会跨过业务边界而不触发告警;翻译异常时保留根因链,并显式重新判定可重试性。
- 约定不是执行约束(B02):
AGENTS.md在模型输入侧,只能提高概率;硬约束必须有模型外的策略执行点。判据是”Agent 违反时系统会不会阻断”。 - 卡片是提示,运行时是权威(S01):预连接发现解决”连哪里”,
server/discover与 list 解决”事实是什么”;卡片 MUST NOT 作为安全或访问控制依据。 - 会话恢复不校验证书(S02):TLS 1.3 恢复握手不发送
Certificate/CertificateVerify,因此会话寿命才是真正的安全边界;吊销生效时延必须单独度量。 - 判圈靠相对速度,不靠记忆(L01):
a ≡ c (mod L)是两阶段 Floyd 算法的全部根据;可迁移为 Agent 轨迹的状态指纹循环检测。
统一主线:今天 5 道题都在回答同一个问题——带作用域和有效期的东西失效时,谁负责感知、感知后传播到哪一层、以及有没有概率性组件之外的确定性兜底。B01 是根因在传播中丢失;B02 是约定在层级覆盖中丢失;S01 是卡片在缓存与漂移中失真;S02 是证书在会话复用中失去检查机会;L01 是回路在缺少速率差时无法被察觉。
本次运行信息
配置读取状态:成功。读取 better/题库配置.md,version: 6,last_maintained: 2026-10-19。本文件只读取、未修改。
- 启用中的一级类型 13 个,全部为
active;暂停 / 合并 / 退役类型均为 0。 - 「观察中的新技术」共 8 项,均未标记”允许试学”,本次未从中出题。
- 按 v6 每日配额执行:非 Agent 基础 1 / Agent 基础 1 / Agent 场景 1 / 非 Agent 场景 1 / LeetCode 1,共 5 道。
- v6 记录「Server Card 纳入启用范围」,并注明自 2026-10-20 起生效;本次 S01 首次从该新增范围出题。
历史读取与去重:
- 已读取
题目索引.md(45 条,indexed_runs: 7)与精选/README.md(4 条)。 - 已读取最近 30 天训练文件的题目层级与一级类型,未逐字读取更早的正文。
- 去重窗口:2026-09-20 至 2026-10-20。
- 去重结果:本次 5 道题与窗口内 45 条记录在题目标题、一级类型、二级主题、核心考点、场景约束、故障现象、预期结论与 LeetCode 题号各维度上均无重复;语义相似度检查通过。
- 与近期题目的显式区分:
- B01(编程语言与运行时 · 异常链)区别于 2026-10-13 B02(异步并发与结构化并发)与 2026-10-15 B02(序列化与信任边界);同时与 2026-10-15 S01 的”网关层错误契约”分层——本题聚焦语言内异常传播与根因保留,不涉及跨服务错误码映射。
- B02(AGENTS.md)区别于 2026-10-14 B04(Agent Skills 渐进式披露):前者是常驻、项目级、无条件加载的约定文件,后者是按需触发、渐进披露的能力包;本题核心是”约定不是执行约束”。
- S01 为配置 v6 新增的场景题类型(跨组织 MCP 服务对外发布),此前 15 类已用场景题中无同类。
- S02(mTLS 证书轮换与会话恢复)区别于 2026-10-16 B01(持久连接与三类超时,考察点是池容量与超时分层)与 2026-10-19 S02(优雅终止与端点摘除,考察点是终止时序);本题核心是信任状态的重新检查机会与吊销生效时延。
- L01 选择链表 / 快慢指针,避开已用的哈希表 + 双向链表(146)、滑动窗口(3)、堆与快速选择(215)、单调栈(739)、二分答案(875)、树形 DP(124)、图拓扑排序(207),以及已删除未登记的 322(一维 DP)。
本次参考的历史题目来源:
better/题目索引.md(45 条,截至 2026-10-19)better/精选/README.md(4 条)better/2026-10/下 2026-10-10 至 2026-10-19 共 7 份训练文件的题目层级与分类信息
覆盖情况:本次新增后,上下文工程与能力封装 3 次、工具与互操作协议 5 次、操作系统与网络 5 次、编程语言与运行时 3 次。覆盖次数仍偏少的方向:Agent 评测与可观测性(2)、LLM 应用工程(2)、AI 与大模型基础(3),可作为后续轮换优先项。
到期提醒:Q-20261013-B03(混合检索融合与重排归因)建议首次复习日为 2026-10-20,角度从”定义与选型”转为”故障定位”(线上正确率 71% → 58% 的四层归因)。按每日任务规则,复习题不计入当日 5 题配额,本次未生成;如需练习可自行从 精选/2026-10-13-B03-混合检索融合与重排归因.md 取用。
精选:本次未收到明确精选意图,未执行任何精选操作。
远端同步:内容仅写入本地 Obsidian 目录,未做远端同步验证,不声称已同步。