# Protocol API Simulation

> For authorized browser-protocol interoperability testing and stateful flow reproduction with curl_cffi. Use when reproducing OAuth or signup/login continuations, challenge-token preflights, PKCE, email or out-of-band verification, 302 redirect chains, cookie or session extraction, callback parsing, JWT or JWE artifact analysis, or when converting a fragile multi-step web flow into a resumable state machine without bypassing risk controls.

- Skill: `misonl/protocol-api-simulation` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add misonl/protocol-api-simulation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/misonl/protocol-api-simulation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: misonl (https://skillmd.com/u/misonl)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/misonl/protocol-api-simulation

---


# Protocol API Simulation

## 何时使用

在以下场景触发本技能：

- 已获得书面授权，需要复现实站或沙箱中的浏览器协议链路。
- 需要用 `curl_cffi` 模拟浏览器级传输特征，定位 OAuth、注册、验证邮件、302 跳转、JWT 提取等问题。
- 需要把易碎的多步骤流程整理成可重试、可恢复、可审计的状态机。

以下场景不适用：

- 绕过验证码、设备绑定、限频、账号风控或平台反滥用控制。
- 未经授权抓取令牌、批量注册、账户接管、凭证撞库或任何规避检测的自动化。

## 核心立场

- 引擎基线：使用 `curl_cffi`，选择单一且稳定的浏览器 profile，例如 `impersonate="chrome"` 或目标环境对应的固定版本，目的是复现已授权浏览器客户端的传输层行为，而不是隐藏自动化身份。
- Session 强一致性：从第一跳到令牌交换，全程复用同一会话上下文，包括 cookie jar、连接池、PKCE 材料、挑战工件、重定向历史和邮件关联键。
- 成功优先判定：一旦 Step 12 的核心资源或核心会话已经被服务端确认提交，立即把该动作视为已提交，后续只做收尾、取证和幂等保护，不再回滚猜测。
- 多段 JWT 自动探测解析：自动从响应头、正文、URL、片段、Cookie、302 Location 中提取候选令牌，解析声明但不信任未验证内容。

## 三分模型

把这类任务拆成三个层面，不要混在一起推理：

- 协议逻辑
  - 关注授权流程、公理、不变量和状态迁移。
  - 例子：`state`/`code_verifier` 是否匹配，callback 是否属于当前轮，创建成功后是否还需要补 token。
- 传输实现
  - 关注具体执行器如何发送请求和保持上下文。
  - 例子：`curl_cffi` session、浏览器 profile、HTTP 版本、cookie jar、代理、重定向处理。
- 观测器
  - 关注你依据什么判断事实成立。
  - 例子：原始请求三元组、302 链、日志、抓包、真实浏览器基线、mitmproxy 记录。

执行规则：

- 协议逻辑是控制器，传输实现是执行器，观测器是传感器。
- 如果问题出在协议逻辑，不要只调浏览器指纹或 HTTP 参数。
- 如果问题出在传输实现，不要误以为协议状态机本身错了。
- 如果事实不清，优先补观测器，再决定是否调整协议逻辑或传输实现。

## 参考导航

- 只需要高层流程、边界和输出要求时，读本文件即可。
- 如果任务命中特定产品或站点实现细节，再按需读取对应参考文件，例如 `references/codex-console-lessons.md`。
- 如果任务涉及安全演练、流量录制、重放、隔离实验环境或证据工件管理，再按需读取 `references/security-lab-lessons.md`。
- 只有在任务真的命中这些坑型时才加载参考文件；不要把任何站点的私有行为默认套到别的目标上。

## 规范优先

默认先以协议规范和通用不变量为主，再记录目标站点的偏差行为。

执行规则：

- 先问“这条链路按 OAuth/OIDC/HTTP 语义本应如何工作”，再问“这个站点实际上做了什么”。
- 如果站点行为偏离规范，不要直接改写主状态机定义；先单独记录为“站点偏差”。
- 主 skill 只沉淀通用规律；站点特有参数、页面流转、缓存兜底习惯下沉到 `references/`。
- 当站点偏差和规范冲突时，输出里同时保留“规范预期”和“站点现实”，不要只保留后一者。

## 首轮 5 问

首次使用时，先回答下面 5 个问题，再决定要不要跑满后续闭环：

1. 当前任务的授权范围是什么：域名、账号、租户、允许副作用边界是否明确。
2. 这轮更适合 `observe_only`、`replay_only` 还是 `mutating_run`。
3. 当前轮关键绑定材料是否齐全：`state`、`code_verifier`、cookie jar、challenge 工件、callback。
4. 当前环境是否可复位，还是只能做录制、抓包和证据保留。
5. 这轮最想确认的事实是什么：协议逻辑、传输实现，还是观测器缺失。

如果前 3 问里有任一项答不上来，默认不要进入 `mutating_run`。

## Control Contract

在真正续跑前，先用下面 5 行写清本轮控制合同：

- `primary_setpoint`
  - 本轮最主要的目标变量，例如“证明 callback 是否属于当前轮，并且不引入新增副作用”。
- `acceptance`
  - 哪些证据能证明目标达成，例如当前轮 callback、302 链、cookie 演进、token exchange 结果。
- `guardrail`
  - 哪些护栏不能被打破，例如授权范围、单用户限制、不可复位环境、共享实例边界。
- `rollback_trigger`
  - 一旦出现什么信号就停止推进，例如 `source_conflict=true`、callback 冲突、风控页、只能依赖 `cached_fallback`。
- `boundary`
  - 本轮允许触碰的动作边界，例如只允许 `observe_only`，或只允许当前轮无副作用重放。

要求：

- `primary_setpoint` 只能有一个，避免一轮里同时追“拿 token”“确认 session 归属”“补全浏览器对照”三个目标。
- `guardrail` 和 `boundary` 先于“想不想跑通”，一旦冲突，默认保守处理。
- 没写这 5 行时，不进入 `mutating_run`。

### 只读核验模板

如果用户明确要求“只做被动确认”“只读核验”“只做留证”，直接复用下面模板：

```text
primary_setpoint: 仅确认当前轮事实是否成立，不引入任何重放或副作用
acceptance: 当前轮 callback / 302 / cookie / challenge 来源能够自洽，不依赖 cached_fallback
guardrail: 不做 resend，不做 create/update/delete，不跨轮补造关键绑定材料
rollback_trigger: source_conflict=true、callback 冲突、关键字段只能由 cached_fallback 解释、风控阻断
boundary: 固定 observe_only
```

执行规则：

- 这种场景下不要把 `replay_only` 当成“只读”的近似替代；文义上写了只读，就固定停在 `observe_only`。
- 输出里显式写明“本轮目标是核验，不是推进”。

### 首次使用最短路径

首次上手时，默认按下面 4 步进入，而不是先通读全文件：

- 先确定当前模式和授权范围。
- 再写状态映射表与最小观测清单。
- 然后只保留当前轮证据，不急着续跑。
- 最后根据绑定一致性与复位条件，决定是停在观测、进入重放，还是阻断。

## 16 步闭环

推荐按下面的闭环组织流程，并为每一步定义输入、输出、失败分类和可重试性：

使用边界：

- 这是分析框架，不要求所有任务都跑满 16 步。
- 对只做被动观测的任务，可在 Step 4 或 Step 14 停止，不必进入创建、token exchange 或提交屏障相关步骤。
- 对“资源或会话已存在，只需补授权或补 token”的续跑任务，可直接从 Step 11 开始，只要当前轮 PKCE、callback 和会话材料完整可证。

1. IP 校验：确认出口地址、地域、授权环境和目标 allowlist 状态。
2. 环境预热：访问首页或引导接口，建立首批 cookie、CSRF 和缓存上下文。
3. 基线采样：记录首轮响应头、协议版本、Set-Cookie、关键脚本入口和初始跳转链。
4. 挑战工件提取：从 HTML、内联脚本、JSON boot payload 或异步接口中提取防滥用、预检或挑战参数。
5. PKCE 挑战生成：创建 `code_verifier` 与 `code_challenge`，并把原始 verifier 持久化到会话状态。
6. 预检提交：发送预检查或初始化请求，确认会话已进入可继续状态。
7. 身份引导：提交最小必要字段，触发邮箱或外部验证链路。
8. 邮件异步轮询启动：记录关联键，进入可中断轮询。
9. 邮件轮询解析：提取验证链接、一次性代码或中间态令牌。
10. 验证链接标准化：展开包装链接，修正 HTML 编码、追踪参数和相对路径。
11. 授权链继续：带着原始 session、PKCE、挑战工件和邮件产物继续推进。
12. 提交确认：一旦服务端确认核心资源或核心会话已经提交，立即标记 `committed=true`。
13. JWT Payload 提取：从当前响应和历史跳转中扫描并解析候选 JWT 或 JWE。
14. 递归 302 追踪：按规范追踪 `Location`，直到终态页面、授权码或终态错误。
15. OAuth Token 交换：仅在授权码和 PKCE 材料完整时执行 token exchange。
16. 终态校验：确认资源状态、令牌来源、失败点、证据链和后续人工复核点。

说明：

- 在 OAuth 或 OIDC 续跑场景里，Step 12 可能表示“授权会话已提交”而不是“新资源已创建”。
- 进入 Step 12 之后，可以继续做 token 收尾、证据补齐和幂等确认，但默认不回到会再次触发创建或注册的上游步骤。
- 如果需要重新触发创建、注册或发码，视为新一轮链路，先封存旧轮证据，再重新开始。

## 启动模板

开始排障或复现前，先写两张短表，不要边跑边临时猜。

### 状态映射表

把目标实现里的本地状态映射到 skill 状态，至少填这四列：

| 本地状态或函数 | skill 状态 | 进入证据 | 下一跳 |
| --- | --- | --- | --- |
| 例如：返回登录页 / 某个 page.type / 某个回调函数 | `WARMUP` / `CHALLENGE_READY` / `WAITING_MAIL` 等 | 当前响应、日志、cookie 或 URL | 下一次请求或检查点 |

要求：

- 只要代码库里存在多条收尾分支、兜底分支或重登分支，就必须先画这张表。
- 如果某个本地状态已经越过创建动作，但 token 仍未到手，明确标记为 `RESOURCE_COMMITTED` 之后的状态，而不是误判为 `DONE`。

### 最小观测清单

默认至少记录这些字段：

- 会话标识：`session_id`、设备标识、cookie jar 摘要
- 挑战材料：挑战参数、证明材料摘要、对应请求的目标 URL
- OAuth 起点：`auth_url`、`state`、`code_verifier`、`redirect_uri`
- 页面或阶段标识：当前页面类型、阶段名、关键响应字段
- OTP 或外部验证：`otp_sent_at`、关联键、收到的验证码或验证链接摘要
- 继续链路定位符：租户、工作区、continue URL、302 `Location`
- callback：`callback_raw_url`、query、fragment、错误字段、授权码摘要
- token 结果：token exchange 成功或失败原因、当前已拿到的 token 类型

如果这些字段里有任何一个只能从旧缓存拿到，而不是从当前响应或当前 cookie 拿到，显式标记为 `degraded_path=true`。

执行顺序：

- callback 解析优先看当前轮 callback URL，再看当前轮 302 `Location`，最后才回看历史响应或缓存。
- `continue_url`、`workspace`、`session_token` 优先从当前响应和当前 cookie 获取，其次看当前轮 302 `Location` 或 callback，最后才允许 `cached_fallback`。
- `state` 和 `code_verifier` 只能来自当前轮生成材料，不能从缓存恢复。

#### 采样计划表

开始前至少补下面 4 列，不要只写“我会看日志”：

| 观测对象 | 主采样点 | 证据强度 | 是否允许驱动执行 |
| --- | --- | --- | --- |
| callback | `current_callback` / 当前轮 `Location` | 强 | 是 |
| PKCE 材料 | 当前轮生成材料 | 强 | 是 |
| cookie / session | `current_cookie` / `Set-Cookie` / 请求 cookie | 强 | 是 |
| continue URL / workspace | `current_response` / `current_cookie` / `current_callback` | 中 | 是 |
| 邮件 / OTP | 当前轮到件、关联键、发送时间 | 中 | 仅用于续跑判定 |
| 旧缓存 / 旧日志 | `cached_fallback` | 弱 | 否 |

执行规则：

- 强证据用于驱动执行与终态判断。
- 中证据可用于辅助续跑，但不能覆盖强证据冲突。
- 弱证据只用于诊断、对照和问题定位，不能单独驱动 token exchange 或模式升级。

#### 常见断点映射表

遇到下面几类高频场景时，优先用这张表落 `breakpoint`，不要临时发明状态名：

| 真实场景 | 推荐 breakpoint | 说明 |
| --- | --- | --- |
| 首页预热、首批 cookie 刚建立 | `WARMUP` | 还没进入 challenge 或授权主链 |
| challenge / 预检材料已拿到，但未提交身份 | `CHALLENGE_READY` | 可继续，但还没进入身份引导 |
| OTP 已发出，等待邮件或外部验证 | `WAITING_MAIL` | 重点是时滞预算，不是重试 |
| 邮件已解析，准备恢复授权链 | `MAIL_PARSED` / `FLOW_RESUMED` | 邮件内容已成为当前轮输入 |
| 资源已创建，但还在补 callback 或 token | `RESOURCE_COMMITTED` 之后的 `RESUME_AUTH` / `WAIT_FOR_CALLBACK` | 不回到会重复创建的上游 |
| callback 已到，但 state / PKCE / 来源冲突 | `WAIT_FOR_CALLBACK` + `BLOCKED` | 先阻断，不推进 exchange |
| token exchange 完成，等待终态核验 | `TOKEN_EXCHANGED` | 重点转为来源核验与结论固化 |

### 来源证据链

对每个关键字段，再额外记录“它是从哪里来的”，至少使用下面四类来源标签之一：

- `current_response`
  - 来自当前请求的响应体、响应头或 `Location`
- `current_cookie`
  - 来自当前轮 cookie jar 或当前轮请求上下文
- `current_callback`
  - 来自当前轮 callback URL 的 query 或 fragment
- `cached_fallback`
  - 来自旧缓存、旧日志、旧文本或上一轮链路残留

执行规则：

- 只要关键字段的来源是 `cached_fallback`，默认把该字段视为降级恢复，不视为强一致性证据。
- 如果 callback、continue URL、workspace、session token 中任一关键字段只能由 `cached_fallback` 提供，显式记录“跨轮污染风险”。
- 做根因判断时，优先比较“字段值是否一致”，再比较“字段来源是否一致”；值对但来源错，仍然不能判主链健康。
- `auth_url`、`state`、`code_verifier` 只能来自当前轮生成材料；旧缓存只能用于诊断，不得驱动 token exchange。
- 如果 callback 只能由上一轮缓存或上一轮 OAuth 上下文解释，直接进入降级或阻断状态，不继续假定当前轮链路健康。
- 对 `continue_url`、`workspace`、`session_token` 这类继续链路定位符，优先使用当前轮可验证来源；若多个当前轮来源互相冲突，停止推进并记录 `source_conflict=true`，不要静默择一。
- 对注册/登录续跑场景，默认提取顺序是：`current_response` > `current_cookie` > `current_callback` > `cached_fallback`；若目标字段语义上只应出现在 callback，则单独说明该例外。
- 只要 `degraded_path=true`，最终结论默认只能写弱结论，不宣称“强 session 一致性成功”。
- 只要授权码、continue URL、会话令牌或 PKCE 材料依赖 `cached_fallback`，默认停止 token exchange，先补当前轮观测。
- 一旦记录 `source_conflict=true`，固定进入 `BLOCKED`，先固化最小证据包，再决定是否重新开一轮授权；不要继续 token exchange 或继续推进当前轮。

#### 执行绑定材料决策表

| 字段 | 允许作为执行依据的来源 | 是否允许 `cached_fallback` 驱动执行 | 缺失或冲突时的动作 |
| --- | --- | --- | --- |
| `auth_url` | 当前轮生成材料 / `current_response` | 否 | `BLOCKED`，重开当前轮 |
| `state` | 当前轮生成材料 / `current_callback` | 否 | `BLOCKED`，不做 token exchange |
| `code_verifier` | 当前轮生成材料 | 否 | `BLOCKED`，不做 token exchange |
| `callback_raw_url` | `current_callback` / `current_response` | 否 | 保留原始输入并阻断 |
| 授权码 | `current_callback` / `current_response` | 否 | 阻断，补当前轮观测 |
| `continue_url` / `workspace` / `session_token` | `current_response` / `current_cookie` / `current_callback` | 否 | 标记 `degraded_path=true`，停止 token exchange |

执行说明：

- `cached_fallback` 可以帮助定位问题，但只能作为取证和对照输入，不能当作“当前轮绑定仍然成立”的证明。
- 如果字段只能从旧缓存恢复，输出里要保留其值和来源，但动作上按“不能继续执行”处理。

### 最小证据包

每次运行至少保留下面四类工件：

- 范围声明
  - 域名、账号、租户、允许副作用范围、当前执行模式。
- 原始工件
  - 抓包流、HAR、302 链、邮件原文摘要、关键请求与响应摘录。
- 归一化状态
  - `state`、`code_verifier`、cookie 摘要、continue URL 来源、callback 摘要。
- 运行结论
  - 当前归因、断点、`degraded_path=true` 与否、是否命中提交屏障。

如果只有最终日志，没有原始工件和来源标签，默认把该结论视为弱证据。

### 执行模式分层

把每次运行明确标成下面三种模式之一，不要混着执行：

- `observe_only`
  - 只做被动观测、录制、抓包、日志对齐，不发送会改变服务端状态的请求。
- `replay_only`
  - 只重放已观察到、且不会引入新副作用的最小请求集，用于验证状态绑定、cookie 演进、302 语义和回放一致性。
  - 不补造未观测字段，不触发 resend，不调用 create/update/delete，不引入新跳转链路。
- `mutating_run`
  - 允许创建、更新或触发真实副作用，但前提是目标环境可复位，或用户已明确批准在一次性测试租户中执行。

执行规则：

- 把三种模式视为硬阶段：Phase 1=`observe_only`，Phase 2=`replay_only`，Phase 3=`mutating_run`。
- 默认从 `observe_only` 开始，证据不足时再升级到 `replay_only`，最后才进入 `mutating_run`。
- 输出中必须显式写明当前模式、升级原因和退出条件。
- 授权范围不清、环境不可复位、或存在单用户限制时，不自动进入 `mutating_run`。
- 只有同时满足“范围已确认”“重放目标已录制”“当前轮关键字段可绑定”“不会引入新增副作用”四个条件时，才允许从 `observe_only` 升到 `replay_only`。
- 只有同时满足“复位方法可验证且已实测或有追溯记录”“实例或租户隔离已证明”“副作用请求具备幂等或可回收识别键”“升级前的最小证据包已落盘”四个条件时，才允许从 `replay_only` 升到 `mutating_run`。
- 共享实例、单用户实例、或复位能力未知且不可验证时，默认停在 `observe_only`；只有用户显式缩小到只读核验范围时，才允许进入 `replay_only`。
- 如果用户明确写明“只做被动确认”“只读核验”“只做留证”或等价边界，固定停在 `observe_only`，不进入 `replay_only`。
- 无法证明“副作用可回收”时，不做 best-effort 续跑；停在 `observe_only` 或 `BLOCKED`。

### 时滞预算

这类链路天然存在时滞，不要把“还没到”误判成“已经失败”，也不要无限等待。

默认预算：

- 302 链追踪：一轮内完成；超过预期深度或出现环路就停止并记录。
- callback 等待：只等当前轮可验证来源；如果只能靠旧缓存解释，立即阻断。
- 邮件 / OTP：允许按用户或环境约定做有限轮询；超过预算后转为 `BLOCKED` 或 `human_required`，不自动 resend。
- 真实浏览器对照：一旦命中“必须升级观测”条件，本轮优先补观测，不在纯 HTTP 侧继续盲试。

执行规则：

- 等待预算要写进输出，不要只写“稍后再试”。
- 时滞超限后的默认动作是停在安全检查点，而不是扩大副作用范围。
- 如果目标系统的实际时滞已知，用目标系统口径覆盖默认预算，并在输出里注明来源。
- 如果目标系统没有提供实测时滞口径，不要自造固定秒数；输出里明确记为“时滞口径待补充”，并按保守预算处理。

推荐记录格式：

```text
delay_source: 目标系统文档 / 实测样本 / 用户说明 / 未知
expected_window: 例如 2m-5m / 单轮 302 内 / 当前轮 callback 即时返回
checkpoints: 例如 30s / 60s / 120s / 300s
timeout_action: BLOCKED / human_required / 升级观测 / 开新一轮
```

执行规则：

- `checkpoints` 只用于观察，不自动绑定 resend 或模式升级。
- 如果 `delay_source=未知`，默认不给“30 秒失败”这种硬判断。

规则优先级：

1. 用户显式边界高于其他一切规则。
2. 安全与复位约束高于“想继续跑通”的诉求。
3. 只要命中 `BLOCKED` 条件，就停止推进，不再用其他条目覆盖。

模式切换闸门：

- `observe_only -> replay_only`
  - 已拿到当前轮证据，且知道要验证哪一个绑定关系或哪一段跳转链。
- `replay_only -> mutating_run`
  - 已确认副作用范围、复位方法、单用户约束和幂等保护。
- 任意模式 -> `BLOCKED`
  - 风控阻断、人工验证、环境不可复位、或关键来源只能依赖 `cached_fallback`。

每次切换模式时，输出里至少补三项：

- 为什么升级或降级
- 当前允许的操作是什么
- 如果失败，如何回到上一个安全检查点

### 实验环境与复位点

进入任何会产生副作用的运行前，先确认下面四件事：

- 隔离边界：测试账号、租户、工作区、实例是否彼此独立。
- 复位方法：如何删除测试资源、回收验证码、清空 cookie、重置邮箱关联键。
- 单用户约束：目标如果天然是单用户或单会话应用，不要让多个测试链路共享同一实例。
- 密钥口径：同一轮实验用到的测试密钥、flag key、回调域或 allowlist 口径必须一致。

如果这些前置条件不成立，把当前任务视为高风险观测任务，只做证据采集，不做自动续跑。复位能力未知时，默认等同于“不可写入环境”。

示例：

- 共享测试实例不等于可续跑；若账号、租户或复位边界不能独立证明，则默认按不可复位处理。
- 不可复位环境不允许 `mutating_run`。

### OTP 与重登卫生

当链路包含 OTP、验证邮件或“注册成功后仍需重登”时，默认遵守下面规则：

- 先判断服务端是否已经自动发信，再决定是否允许 resend；不要把 resend 当默认动作。
- 记录 `otp_sent_at`、邮箱关联键、最近一次成功解析结果和对应会话标识。
- 新一轮 OTP 或重登开始前，清理上一轮遗留的 continue URL、workspace、session token 摘要和邮件解析缓存。
- 一旦进入 `RESOURCE_COMMITTED`，后续默认只允许收尾、重登补 token 或取证，不回到会重复创建资源的上游状态。

自动发送判定顺序：

1. 先看 `current_response` 是否明确出现“已发送验证码/验证邮件”或等价阶段迁移。
2. 再看 `current_cookie`、`current_callback` 或当前轮页面状态里是否出现新的邮件关联键、发送时间或验证阶段标识。
3. 再看当前轮邮箱、短信或外部验证通道里是否出现与本轮会话一致的到件证据。
4. 只有前三步都无法证明“已自动发送”，且目标系统允许 resend，才把 resend 视为候选动作。

执行规则：

- 任一当前轮证据已经证明“已自动发送”时，不再自动触发 resend。
- 只有用户明确批准、且 resend 不会污染当前轮绑定时，才把 resend 视为单独的 `mutating_run` 动作。
- 只要 resend 会引入新的验证码、覆盖旧验证码或改变 continue URL，就把它视为副作用步骤，而不是普通重试。

## 实现准则

### 传输层模拟

- 使用单一 `curl_cffi` session 对象承载全流程。
- 在同一闭环中固定一个浏览器 profile，避免中途切换指纹配置。
- 保持请求顺序、来源域、重定向承接和 cookie 演进与真实浏览器一致。
- 对 302、303、307、308 分别按 HTTP 语义处理方法保留或转换，不要偷懒统一成 GET。
- 只在授权要求内使用代理，并把代理信息纳入审计记录。

### 会话强一致性

会话状态至少包括：

- `session_id`
- `cookie_jar`
- `challenge_artifacts`
- `pkce_verifier`
- `pkce_challenge`
- `redirect_chain`
- `mail_correlation_key`
- `committed`
- `jwt_candidates`

要求：

- 任一步骤若需要新建 session，必须视为异常而不是正常分支。
- 任何重试都应从最近一个可恢复检查点继续，而不是丢失前态重新猜测。
- 所有副作用步骤都要带幂等键或可重复识别键。

### 每轮授权请求唯一性规则

把下面这些对象视为“当前轮授权上下文”的一组绑定材料：

- 当前轮 `auth_url`
- 当前轮 `state`
- 当前轮 `code_verifier`
- 当前轮挑战工件
- 当前轮 cookie jar
- 当前轮 callback URL

### 绑定判定表

用下面的口径判断“是不是还在同一条链路上”：

| 对象 | 保持一致时的信号 | 污染或跨会话信号 |
| --- | --- | --- |
| 会话对象 | 当前请求沿用同一 cookie jar 和连接上下文 | 中途重建 session 后继续复用旧材料 |
| 设备或挑战上下文 | 当前挑战材料与当前设备标识、cookie、预检链对应 | 旧挑战材料被拿到新 session 里继续用 |
| OAuth 上下文 | 当前 callback 能被当前轮 `state` / `code_verifier` 接受 | callback 只能被上一轮或另一轮 OAuth 上下文解释 |
| 继续链路定位符 | 当前租户、工作区、continue URL 来自当前响应 | 只能依赖旧缓存、旧文本或旧重定向结果兜底 |
| 会话令牌 | 能从当前 cookie 或当前响应直接恢复 | 只能从旧日志、旧文本或兜底拼接恢复 |

判定规则：

- 只要关键字段发生来源切换，就不要只写“还能跑通”，而要标记为“链路已降级”。
- 只要当前链路主要靠缓存兜底继续，就不要把它当作强 session 一致性的成功样本。

### Callback 判定规则

- 如果 callback 同时包含 query 和 fragment，分别解析两侧的 `code`、`state`、`error`，不要只取一边。
- 如果 query 与 fragment 给出的关键字段互相冲突，把该 callback 视为无效证据，进入 `BLOCKED`，并保留原始输入。
- 如果授权码存在，但当前轮 `state` 或 `code_verifier` 缺失、不匹配或只能由旧缓存解释，停止 token exchange，进入 `BLOCKED`，并保留原始输入。
- callback 只能证明“当前轮可继续”，不能自动证明“当前轮已健康闭环”；是否健康仍要看字段来源和会话绑定。

### 多段 JWT 自动探测解析

自动扫描这些位置：

- `Authorization`、`Set-Cookie`、自定义头
- HTML 正文、JSON 字段、内联脚本
- URL query、fragment、302 `Location`
- 邮件正文和验证链接

解析规则：

- 候选格式优先识别三段 JWS 和五段 JWE。
- 对每一段执行 base64url 安全解码，失败即标记为非 JWT。
- 只有当候选字段同时满足“分段数正确”“字符集符合 base64url”“解码后头部或声明呈 JSON 对象”时，才升级为 `candidate_token`。
- 对非标准 base64、随机长字符串或无法满足 JWT/JWE 结构约束的字段，只标记为 `suspected_token`，不自动判真。
- 仅把 payload 作为线索，不把未验证声明当作真实权限依据。
- 记录令牌来源位置、发现步骤、掩码后的头部与声明摘要。
- 对候选按“当前轮 callback > 当前轮响应 > 当前轮 cookie > 旧缓存”排序；旧缓存候选默认最低优先级。
- 对内容相同但来源不同的候选不做无脑去重；来源差异本身也是证据。
- 对每个候选统一输出：`source`、`location`、`token_type`、`mask`、`parse_status`、`payload_summary`、`verification_status`。

### Token 三层分离

把 token 相关工作拆成三层，不要混为一谈：

- 结构层
  - 先判断它是不是 JWS / JWE / 其他 bearer token，能否被安全解码。
- 校验层
  - 再判断签名、issuer、audience、nonce、过期时间或相关校验条件是否成立。
- 权限层
  - 最后才根据已校验的声明或服务端返回结果判断权限、身份和可用性。

执行规则：

- 结构层通过，不代表 token 可信。
- 校验层失败时，权限层默认不能继续给出正向结论。
- 未验签 payload 只能用于排障线索、字段发现和状态定位，不能用于声称“这个 token 有某权限”。
- 如果目标链路同时返回 `id_token`、`access_token`、`refresh_token`，分开记录，不要把它们混成一个“token 已拿到”的笼统状态。

### 递归 302 追踪

- 追踪时保留跳转深度、来源响应、目标 URL、状态码和方法变化。
- 设定最大深度与域名边界，防止无限跳转和跨域漂移。
- 命中终态条件后停止递归：授权码到达、终态页面到达、显式错误到达、深度上限达到。
- 任何跳转中的令牌、验证码、JWT 候选都要在到达终态前先行提取。
- 默认顺序是：先记录当前响应中的候选 token 和 callback 线索，再继续追 302，最后才决定是否 token exchange。

### 升级观测条件

当纯协议重放已经不能解释事实时，升级观测器，而不是继续猜。

优先升级到这些高可信观测手段：

- 真实浏览器基线
  - 用于确认页面真实流转、前端脚本行为、JS challenge、WebAuthn、复杂 cookie 演进。
- 抓包或拦截代理
  - 用于确认真实请求顺序、头部差异、302 链、挑战参数来源、缓存命中与否。
- 对照实验
  - 把“纯 HTTP 模拟”和“真实浏览器基线”并排比较，定位差异出在哪一层。

默认升级条件：

- 当前链路事实主要依赖猜测，而不是当前观测证据。
- challenge、验证码、302 或 callback 的来源无法证明属于当前轮。
- 纯 HTTP 客户端无法解释真实浏览器才能触发的页面状态或脚本行为。
- 多轮重试后仍无法分清问题是在协议逻辑、传输实现还是观测缺失。
- 真实浏览器基线与纯 HTTP 模拟的差异已经出现，但还没有明确标注差异落在协议逻辑、传输实现还是观测器。

命中下面任一项时，升级观测不再是建议，而是必做：

- 真实浏览器与纯 HTTP 的 302 `Location` 不一致
- 任一关键 `Set-Cookie` 只出现在其中一条链路
- callback 的 query、fragment 或错误字段在两条链路间不一致
- token 或 challenge 工件只在其中一条链路中出现

升级后最少比较这些差异维度：

- 302 `Location` 与最终落点
- `Set-Cookie` / 请求 Cookie 的增删变化
- callback query / fragment / 错误字段
- challenge 参数与证明材料来源
- JWT/JWE 候选的出现位置与结构差异

建议直接按下面的固定对照表头记录，避免每次手写 diff：

| 维度 | 真实浏览器基线 | 纯 HTTP 模拟 | 结论 |
| --- | --- | --- | --- |
| 请求方法 + URL | | | |
| 状态码 + `Location` | | | |
| `Set-Cookie` / 请求 Cookie | | | |
| callback 原始 URL | | | |
| challenge 来源 | | | |
| token / JWE / JWT 候选位置 | | | |

## 风控处理规则

正确做法是识别、留痕、止损、升级。

### 识别信号

- 403、429、设备指纹挑战、验证码、JavaScript 挑战、不可解释的跳转环
- 要求真实浏览器证明、WebAuthn、短信二次验证、人工审核页
- 邮件延迟异常、重复验证码失效、同一会话材料突然失效

### 允许的处置

- 降低并发，严格遵守 `Retry-After` 和官方速率限制。
- 切换到用户提供的测试租户、沙箱环境或 allowlist 环境。
- 请求目标系统提供正式测试入口、测试账号或文档化豁免。
- 把阻断点写入证据链，并停止后续会修改资源的自动操作。

### 禁止的处置

- 使用代理池、住宅 IP、设备农场或打码服务规避控制。
- 伪造浏览器证明、篡改设备绑定材料、重放一次性验证码。
- 伪造或修改 JWT 声明、伪造 OAuth 回调、跳过平台要求的人工验证。

## 自愈状态机设计

把流程设计成显式状态机，不要把重试散落在各函数里。

推荐状态：

- `INIT`
- `WARMUP`
- `CHALLENGE_READY`
- `PKCE_READY`
- `IDENTITY_SUBMITTED`
- `WAITING_MAIL`
- `MAIL_PARSED`
- `FLOW_RESUMED`
- `RESUME_AUTH`
- `WAIT_FOR_CALLBACK`
- `RESOURCE_COMMITTED`
- `JWT_EXTRACTED`
- `REDIRECT_TRACED`
- `TOKEN_EXCHANGED`
- `BLOCKED`
- `FAILED`
- `DONE`

设计要求：

- 每个状态只负责一个可验证目标。
- 每个状态都定义进入条件、成功条件、失败分类、最大重试次数和检查点输出。
- 瞬时失败和永久失败必须分开处理；需要人工接管时进入 `BLOCKED`。
- `RESOURCE_COMMITTED` 是提交屏障。进入该状态后，默认只允许进入收尾、重登补 token、取证或人工复核等后续状态。
- 邮件轮询必须可中断、可续跑、可超时，并保留最近一次成功解析结果。
- 注册成功后若仍需重登或继续授权，应直接进入 `RESOURCE_COMMITTED` 之后的 `RESUME_AUTH`、`WAIT_FOR_CALLBACK` 或同类后续状态。
- 如果确实需要重新触发注册、创建或发码，必须作为新一轮链路显式开启，不能从当前轮状态机直接倒回。
- `16 步闭环` 是分析框架；这里的状态列表是实现状态机。不要把两者当作重复要求，而应保持一一映射关系。

建议把失败分成三类：

- `transient`：网络抖动、上游超时、邮件暂未到达，可在预算内重试。
- `permanent`：参数错误、授权不足、环境不匹配，立即失败。
- `human_required`：风控阻断、多因素验证、策略页拦截，停止自动化并升级。

## 最小输出要求

最终输出至少覆盖下面这 11 行骨架，避免写成散文：

- mode: `observe_only` / `replay_only` / `mutating_run`
- mode_change_reason: 为什么保持、升级或降级到当前模式
- breakpoint: 当前停在第几步，或哪个 skill 状态
- committed: `true` / `false`
- current_round_bindings: 完整 / 缺失 / 污染
- degraded_path: `true` / `false`
- reset_plan: 复位方法 / 不可复位
- strongest_evidence: 当前最强证据来自哪里
- root_cause_hypothesis: `protocol_logic` / `transport_impl` / `observer_gap` / `environment_blocked`
- failure_class: `transient` / `permanent` / `human_required`
- next_safe_action: 下一步最安全动作是什么

## 最小验证矩阵

- 挑战路径：确认请求实际发送了非空证明材料，不接受占位值或空字符串。
- OTP 路径：确认自动发送链路下没有额外的发送或重发调用。
- 重登路径：确认“资源已创建，但 token 需二次登录获取”的分支能闭环。
- callback 路径：确认 query、fragment、302 `Location` 三处都能提取授权码、状态或错误。
- 会话路径：确认租户、工作区、会话令牌或同类关键标识可从 cookie、响应体或缓存检查点恢复，而不是仅依赖单一来源。

## 标准反例

下面这些不是“可讨论分支”，而是默认反例：

- `callback` 的 query 与 fragment 给出冲突的 `state`
  - 结论：`BLOCKED`
  - 不做 token exchange，不靠旧缓存解释通。
- 服务端已自动发 OTP，但邮件存在天然时滞
  - 结论：按时滞预算等待，不自动 resend。
  - 30 秒未到不是失败，只能算检查点。
- 共享实例、单用户、复位未知，且用户要求只读核验
  - 结论：固定 `observe_only`
  - 不进入 `replay_only`，不做任何副作用动作。
- `code_verifier`、`state`、`auth_url` 只能从旧缓存恢复
  - 结论：旧缓存只做诊断，不驱动当前轮执行。
  - 如需继续，开新一轮授权。
- 已进入 `RESOURCE_COMMITTED`
  - 结论：默认只允许收尾、补 token、取证、人工复核。
  - 不回到会重复创建资源的上游步骤。

## 默认执行姿态

- 先求证据，再求推进。
- 先保 session 一致性，再谈重试。
- Step 12 一旦成功即提交，不做侥幸回滚。
- callback 如果只能由上一轮缓存解释，立即标记 `degraded_path=true` 并停止 token exchange。
- `replay_only` 只做非副作用核验，不承担 resend、create、update、delete。
- 风控命中即停，不把规避技巧写进实现。

