notebooklm-py 架构决策深度解析:Cookie、CookieJar、MasterToken 认证领域类型与 AuthTokens 演进路线

发布时间:2026/9/13 15:14:46
notebooklm-py 架构决策深度解析:Cookie、CookieJar、MasterToken 认证领域类型与 AuthTokens 演进路线
notebooklm-py 架构决策深度解析Cookie、CookieJar、MasterToken 认证领域类型与 AuthTokens 演进路线【免费下载链接】notebooklm-pyUnofficial Python API and agentic skill for Google Gemini Notebook. Full programmatic access to NotebookLMs features—including capabilities the web UI doesnt expose—via Python, CLI, and AI agents like Claude Code, Codex, and OpenClaw.项目地址: https://gitcode.com/GitHub_Trending/no/notebooklm-py本文基于 notebooklm-py 仓库的架构决策记录 ADR-0032 展开完整解读该项目如何将_auth认证子系统中散落六种形态的 Cookie 收敛为不可变领域类型并规划AuthTokens从双影子字段走向单一冻结凭证的三阶段演进路线。读完后你将掌握该项目的 cookie 类型体系Cookie/CookieJar/MasterToken、codec 依赖倒置原则以及用equality-pinned 守卫测试锁定公共 API 迁移窗口的工程方法。一、背景名词没有方法边界移动因此变得危险ADR-0032 是 ADR-0031凭证分层认证模型的延伸前者把认证操作从命名函数推进到命名类型。其触发点来自一次名为 #2139 的_auth交叉边界审计——审计发现该子系统中难以归类的耦合全部指向同一个缺口名词没有方法nouns have no methods。具体而言一个 cookie 在认证层中以六种形态存在Playwrightstorage_state.json的原始行DomainCookieMap之外的原始 dictDomainCookieMap(name, domain, path) - value三元组键映射FlatCookieMapname - value扁平映射LegacyDomainCookieMap旧版二元组(name, domain) - value原始 Playwright dict 与 rookiepy 浏览器提取行sanitized entries净化后的条目列表。httpx.Cookies则是第七种形态。而关于 cookie 集合的问题——有哪些名字是否可用绑定关系还在吗——没有类型可以承载只能以散落自由函数的形式存在。这正是 #2139 中看起来是适配器本地函数的名字最终被证实通过共享私有 helper 与核心强耦合、导致边界移动不安全的原因。从源码结构看这一混乱在 cookie_types.py 模块文档 中有直接记录六种形态之间的转换是散落在cookies与cookie_policy模块中的自由函数没有任何类型拥有一组认证 cookie这个概念。二、三个承重发现设计评审中最不直觉的部分ADR-0032 的 Context 部分给出三个承重且不显然load-bearing and non-obvious的发现它们直接决定了后续所有决策的走向发现 1活动 jar 不能是不可变值类型运行中的 cookie 状态是一个httpx.Cookies对象它在每次响应后被传输层就地变异in-place mutation在恢复流程中被_replace_cookie_jar整体替换并在 ADR-0016 的 Auth Instance Invariant 下被多处别名共享。该子系统修复过的每一个硬 bug——两视图陈旧问题Stage-4 同步提交中修复、#2057 heal 循环、附录 A2 的竞态——本质上都是视图间同步synchronization-between-viewsbug。如果把不可变CookieJar作为活动表示的规范形式等于引入第三个需要同步的视图必然制造出下一类同类 bug。这是整个努力被重新框定reframed的关键发现。发现 2same_site在Cookie上是自相矛盾的same_site对持久化是承重的rookiepy →storage_state的往返必须保留该字段否则就会重新打开 #2150 的 SameSite 降级回归。但它又是结构上不可填充的http.cookiejar.Cookie无法携带 SameSite 属性因此任何从 httpx jar 构建的Cookie都只能same_siteNone。矛盾在于Cookie是 frozen dataclass若same_site参与__eq__那么任何经由Cookie.__eq__的快照/基线 diff 都会制造幻影 SameSite 差异绕过_preserved_same_site保护机制。发现 3活动表示无法统一但AuthTokens的形状可以清理由于活动 jar 必须保持为httpx.Cookies系统中永远会存在两种 cookie 表示线上livewire jar 与用于输入/基线/提问的值类型Cookie/CookieJar。本 ADR 的价值是收敛六种输入形态并把问题归属到方法上而非合并这两者。关键点在于这不是AuthTokens的上限。目的地形状见第六节完全移除AuthTokens的 cookie 字段——AuthTokens恰恰通过根本不持有活动 jar而达到干净的冻结形状。三、决策一Cookie值类型——冻结、带槽、脱敏ADR 决定的Cookie是冻结、带槽、脱敏的值。当前实现位于 cookie_types.py与 ADR 描述逐字段吻合dataclass(frozenTrue, slotsTrue, eqFalse) class Cookie: One auth cookie, keyed by its RFC 6265 §5.3 identity. name: str domain: str path: str value: str field(reprFalse) expires: float | int | None None http_only: bool False secure: bool False same_site: str | None field(defaultNone, compareFalse, reprFalse)值得逐点核实的细节same_site携带但不比较field(defaultNone, compareFalse, reprFalse)正是承重保留、永不比较决策的落地——持久化往返能保留它#2150而任何无法填充它的来源如 httpx jar不可能制造差异。自定义相等性eqFalse配合自定义__eq__与__hash__比较字段为(name, domain, path)(value, expires, secure, http_only)恰好就是CookieSnapshotKey CookieSnapshotValue的组合。双重身份投影identity属性返回兼容用的精确普通元组(name, domain, path)key属性返回类型化的CookieIdentityNamedTuplepath默认/供新的值导向代码使用。脱敏value排除在 repr 之外自定义相等性保持断言内省停留在脱敏表示上——cookie 值与凭证等价绝不出现在repr()、日志或 pytest diff 中。四、决策二CookieJar——真正不可变的有序序列CookieJar 被定义为真正不可变、有序的Cookie序列构造时对输入做元组拷贝tuple-copyfrozen slots 拒绝重新绑定或删除。它的用途边界在 ADR 中被写死只用于 cookie 的输入、基线与问题questions——绝不是活动 jar也绝不是Mapping。构造器家族construct构造器输入形态语义from_storage_statePlaywright storage-state mapping应用共享净化器与域名白名单保留源顺序与重复行from_rookiepyrookiepy 浏览器提取行走依赖底部的 snake_case → camelCase 适配与 session 过期约定from_domain_map任意旧版 cookie-map接受三/二元组键与扁平形态map 形态不携带过期/标志位取默认值from_httpx活动httpx.Cookiessame_site-lossy见下其中from_httpx()在 ADR 中的定位非常精确它是纯合并决策whose dirtiness policy 刻意排除 SameSite的有效瞬时活动观察但永远不能作为持久基线或独立文档序列化器——把它经to_storage_state()往返会丢掉sameSite即精确复现 #2150 降级。这个警告直接写进了 实现文档。转换器convert与操作特定的重复裁决重复身份在不同操作下有不同的赢家规则这是本类型设计中最精细的部分迭代与to_storage_rows()保留每一行domain_map_first_wins()与兼容投影to_domain_map()首个身份获胜first-winsto_httpx()的 stdlib 直插精确身份末位获胜last-wins同时保留不同 domain/path 的兄弟条目。ADR 明确不提供to_flat_map或含糊的by_identity投影。源码中的注释 解释了原因扁平化到name - value会折叠 path 分量#369、在同层域名间任选赢家#2054给规范类型提供这个方法等于把该退役模型试图消除的坑带进新模型。需要线上字节的调用方应走to_httpx()它是 path 与 domain 双正确的路线。to_storage_state()同样被刻意定义为过滤后的类型化视图origins为空列表不是无损的 profile 文档往返——需要保留 legacy 全域名与 HttpOnly 观察行为的持久化适配器应显式构建自己的观察。问题方法asknames()、has_secondary_binding()、is_rotatable()、validate_required()、missing_hint()五个方法把原先散落的自由函数收编为方法并统一委托给cookie_policy表。ADR 特别指出cookie_policy保持为模块而非并入类型它在提取失败时被消费那时根本不存在 jar 对象。实现 确认了这层方法委托、策略留模块的结构例如is_rotatable()刻意比has_secondary_binding()更弱委托给_cookie_policy._has_rotatable_secondary_binding。五、决策三codec 依赖方向反转ADR 要求纯 codec 的依赖必须向下指。落地后形成三层 cookie 家族cookies.py兼容自由函数 日志/策略边界薄适配器 ↓ 指向 cookie_types.py值类型只导入叶子与 cookie_policy ↓ 指向 cookie_semantics.py依赖底部的标量/行 codec 叶子cookie_semantics.py 拥有依赖底部的标量/行机制过期与形状规范化如normalize_cookie_expiry处理 Playwright 的-1session 哨兵与毫秒/微秒 rescale、legacy-map 与 rookiepy 适配、HttpOnly 观察、忠实的 stdlib 构建、storage 行序列化。它不做任何策略或日志决策。cookie_types.py 只导入该叶子与cookie_policy从不导入兼容层、持久化、恢复、运行时、CLI 或门面层。这替换了原来向上的cookie_types → cookies依赖边——正是这条反向边曾让移动代码跨边界变得危险。六、决策四MasterToken、ProfileStore、MintService与 Bootstrap 协调器MasterToken纯值不含 I/O当前实现位于 master_token_types.pyemail, android_id, secret加平凡访问器。ADR 划出的红线——无网络、无文件 I/O、无可写性逻辑——在源码中得到印证assert_account_writable读取两个磁盘源且仅是咨询性的权威、无 TOCTOU 的检查在写锁之下进行因此它属于协调器而非值。仓库中该函数确实位于 master_token_bootstrap.pybootstrap 协调器而非值类型模块。__repr__脱敏为MasterToken(email..., android_id..., secretredacted)与CookieJar的脱敏策略一致。secret唯一的序列化形式是master_token.jsonADR 记载其文件权限为 0600。ProfileStore持久化边界与三个显式等价谓词ProfileStore 是持久化边界六笔storage_writer事务一套锁模板来自 ADR-0031 Stage 3、master_token.json读写、快照/delta/CAS 机制。ADR 强调该机制保留三个显式等价谓词且以函数而非Cookie.__eq__存在tuple-dirty排除same_site的脏检测value-only CAS比较拒绝键的基线推进;leading-dot 域名变体匹配.domain与domain拼写变体。Cookie统一的是快照的存储而比较策略留在磁盘状态所在处——这是真实收敛而非重命名ADR 声称已验证不改变 CAS 语义same_site/点前缀变体谓词在带外保留。MintService与 Bootstrap 协调器MintServiceOAuth → MergeSession → RotateCookies → CookieJar纯网络。它是 Tier-0→Tier-1 过渡的服务而不是值类型上的方法。Bootstrap 协调器按序编排MasterTokenMintServiceProfileStore 客户端一个向上依赖并拥有可写性强制。ADR 的诚实表述值得引用它保留的扇出fan-out本就是 bootstrap 编排的固有形态其声称是原来一块的地方变成了三块可测试的东西而非一次干净切割。七、AuthTokens的目的地与跑道目的地形状不持有任何 cookiedataclass(frozenTrue) class AuthTokens: # a BOOTSTRAP credential, not a live-state bag initial_cookies: CookieJar # immutable seed — read ONCE to open the client, never re-read csrf_token: str session_id: str authuser: int account_email: str | None storage_path: Path | None理由HTTP 客户端已经拥有活动 jar 并在每次响应上变异它AuthTokens上的第二份拷贝是需要永远同步的重复事实而子系统修复过的每一个硬 bug两视图陈旧、#2057、附录 A2 竞态都是这种重复的症状。现状公共兼容影子与 equality-pinned 审计当前 AuthTokens 在本发布中形状不变保持位置构造、dataclasses.replace与新增了.jar属性——cookiesmap 输入的类型化投影且明确不是活动 jar。实现文档 明确其身份只读问题视图names/validate_required/has_secondary_binding/missing_hintsame_site-lossy by construction因此永不用于持久化并自述为 v1 中initial_cookies: CookieJar的迁移形态。Phase-A 审计由 test_authtokens_jar_sync.py以相等性钉死equality-pinned该守卫测试用 AST 访问器静态盘点全仓库src/下所有对AuthTokenscookie 影子cookies、cookie_jar、jar、flat_cookies、cookie_header、cookie_header_for的读写将完整清单以 frozenset 常量钉住任何新增或过期条目都会使测试失败。ADR 中的影子访问清单Owner / Shadow access / Role 三列表格与守卫测试中的清单逐条对应例如_web/transport/kernel.py:Kernel._bootstrap_cookies—— 唯一一次 bootstrap 移交读cookie_jar回退cookies在客户端组合时拷入 kernel 所有权_auth/tokens.py:AuthTokens._sync_cookie_jar—— 无警告的内部 v0.x 同步回写所有者AuthTokens.replace_cookie_jar—— 仅废弃的公共 v0.x 兼容边界_web/transport/auth.py:AuthRefreshCoordinator.update_auth_headers—— refresh 后同步公共影子存储 cookie 重载路径在finally中调用_sync_cookie_jar确保即使取消中断基线采纳公共影子也被同步。守卫测试还断言 直接禁止.cookie_jar ...的裸赋值owner 模块除外并验证_sync_cookie_jar必须同时设置两个视图——防止守卫的名字活过了它所验证的东西这一失败模式。ADR 的核心断言是三个第一方_sync_cookie_jar调用点只写不读影子第四处是废弃公共包装器持久化读kernel.cookies账户邮箱路由在 open 前/中/后都读 kernel jar没有任何第一方 post-bootstrap 传输、路由、恢复或持久化决策读 cookie 影子kernel 在 close/reopen 间保留精确的传输 jar不构造分离的代际快照。三阶段跑道阶段性质内容Phase A现在非破坏无公共变化post-bootstrap 读方全部改指kernel.cookies.jar保留为公共投影其迁移角色即未来的initial_cookies形状运行时同步回写被标记为仅兼容用途。此后AuthTokens行为上已是冻结的 bootstrap 凭证只是穿着可变 dataclass 的服装维持公共表面Next minor下一小版本废弃非破坏对flat_cookies普通 property可安全告警的早期预警金丝雀与第一方同步回写迁移后的公共replace_cookie_jar发出运行时DeprecationWarningcookies/cookie_jar/cookie_snapshot仅文档级废弃——因为dataclasses.replace/__eq__/__repr__会读它们运行时警告会从库内部误触发Phase B下一主版本破坏性删除cookie_jar、cookies、cookie_snapshot、公共replace_cookie_jar、内部同步回写、flat_cookies、cookie_header*把AuthTokens冻结为目的地形状。因 Phase A 已确保内部无人依赖这些字段Phase B 退化为干净的字段删除而非逻辑迁移八、后果与诚实的局限ADR 的 Consequences 部分给出了可验证的收益与如实的成本六种输入形态收敛为一个构造器家族问题成为方法使 #2139 不安全的自由函数扇出被私有化到类型背后投影名让重复赢家显式化to_httpx()无 map 折叠地保留过期/session 类型、path、domain 拼写、点前缀域名元数据、secure 与 HttpOnlyCookie在存储层退役并行的CookieSnapshotKey/CookieSnapshotValue比较策略留在磁盘状态处。诚实的局限Honest limits活动 jar 永远是httpx.Cookies传输层拥有它from_httpx是有损构造器bootstrap 协调器的依赖箭头未变——它变小、可测试但没有解耦。成本拆分master_token.py与迁移快照机制会扰动 ADR-0007 的补丁接缝测试在裸名调用点 patch 属主模块移动函数体即移动接缝值/服务拆分引入扁平模块没有的间接层公共表面清理是推迟到主版本的真实破坏性变更Phase A 已将其降险为字段删除。九、被否决的备选方案ADR 记录的六个被拒方案及其理由本身就是理解该决策空间的关键把AuthTokens.cookies/cookie_jar折叠到不可变CookieJar上原计划——被拒让不可变类型充当传输层就地变异的活动表示是保证制造第三视图陈旧 bug的方案这一发现重新框定了整个努力。把快照机制吸收进CookieJar.changes_since()——被拒机制携带删除集、三个非Cookie.__eq__的等价关系、CAS 拒绝键的基线推进与一个过滤不对称吸收它只是重命名会静默改变保存语义。它留在ProfileStore。本发布运行时废弃.cookies——被拒合成的 dataclass 方法读它警告会从replace//repr发出。只能文档级废弃直到字段能变成 property。给MasterToken加mint_session()/is_writable_for()方法——被拒mint 是网络、可写性需要磁盘加锁两者都向纯值泄漏 I/O它们分别是服务与协调器的职责。AuthTokens.cookies变成从cookie_jar派生的 property保留影子、按需计算——作为目的地被拒它保留了影子并计算它没有消除 bug 类别。目标是删除影子而不是派生影子。保留RequestTokens/AccountIdentity——被拒没有行为或不变量支撑这两个类型它们将是重命名而非缩减csrf_token/session_id保留为字段。十、延伸阅读与验证路径ADR-0032 的实现是增量的Status: Acceptedimplementation remains incremental相关决策与代码可在仓库内直接交叉验证前序决策ADR-0031凭证分层模型、ADR-0016Auth Instance Invariant、ADR-0029唯一规范storage_state写者、ADR-0033 与 ADR-0034_auth合并策略与存储对象模型ADR 全目录索引见 docs/adr/README.md。值类型与 codecsrc/notebooklm/_auth/cookie_types.py、src/notebooklm/_auth/cookie_semantics.py、src/notebooklm/_auth/cookie_policy.py、src/notebooklm/_auth/master_token_types.py影子同步守卫tests/_guardrails/test_authtokens_jar_sync.pyequality-pinned 清单、直接重绑定禁令、双视图断言。这篇 ADR 的可借鉴之处超出了 notebooklm-py 本身当系统中某个可变基础设施对象如 httpx 的 cookie jar无法值类型化时正确的做法不是强行不可变而是明确区分输入/基线/问题与活动状态让值类型只服务前者并用可静态验证的守卫测试把公共 API 的迁移窗口钉死在可审计的状态上。【免费下载链接】notebooklm-pyUnofficial Python API and agentic skill for Google Gemini Notebook. Full programmatic access to NotebookLMs features—including capabilities the web UI doesnt expose—via Python, CLI, and AI agents like Claude Code, Codex, and OpenClaw.项目地址: https://gitcode.com/GitHub_Trending/no/notebooklm-py创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考