ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

notebooklm-py ADR-0039 解析:Web 与 Android 双后端凭据面分离设计

notebooklm-py ADR-0039 解析:Web 与 Android 双后端凭据面分离设计 notebooklm-py ADR-0039 解析Web 与 Android 双后端凭据面分离设计【免费下载链接】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-0039: Backend-specific credential surfaces 展开解析该项目 v1 版本中“按后端选择凭据类型”的完整设计Web 后端的AuthTokens引导凭据与 Android 后端的AndroidMasterToken持久凭据如何在构造、存储加载、身份查询与刷新四个环节彻底分家以及凭据失配为何必须成为 I/O 之前的确定性错误。读完本文你将掌握两套凭据族的优先级规则、from_storage工厂的加载契约、refresh_auth()的双后端行为差异以及 C9b 发布门禁对这套 API 变更的约束机制。背景0.x 构造函数为何“逼迫”Android 使用 Web 形态凭据ADR-0039 解决的问题源于 0.x 版构造函数的一个结构性缺陷。在 0.x 中NotebookLMClient构造函数要求传入一个可变、Web 形态的AuthTokens对象——即使实际选定的后端是 Android。其后果是Android 存储构造路径会先加载 Web cookies、再抓取 Web 请求令牌之后 Android 运行时才去读master_token.json根客户端因此暴露出 Web 的 CSRF、session、cookie 与authuser状态而这些对一个真正凭据是持久 master token 的 Android 运行时毫无意义唯一真正需要这个“延迟 Web 兼容 sidecarLazyWebSidecar”的是已弃用的rpc_call()包装器——一个非类型化的 Android 客户端特性。也就是说凭据模型和传输后端出现了类型错位Android 运行时的真实凭据是master_token.json中的持久 master token但公共 API 却强制它经过一套 Web 凭据通道。ADR-0039 的目标就是划清这两族凭据之间的公共边界。前置决策本 ADR 站在哪些既有结论之上ADR-0039 并非凭空设计它显式依赖三个已接受的前置决策ADR-0032Web 侧目的地AuthTokens变为不可变的引导bootstrap值对象字段为initial_cookies可变 cookies 与请求令牌的所有权移交给 Web 运行时。ADR-0023 与 ADR-0034master token 基础设施master token 的持久化、文件锁、脱敏redaction与铸发minting已分别分配给ProfileStore、MasterTokenFile、MintService与 bearer provider。ADR-0039 补上的是最后一块拼图这两族凭据之间的公共边界本身。一个重要的定性前提这是一个 v1 级变更。ADR 的接受只确立目的地destination并不证明告警窗口warning window已经发货。ADR-0039 原文指出包括后续 C3/C4/C5 迁移在内的精确公共与行为断裂都独立地由发布迁移门禁账本把守C9b 门禁未过之前该设计不生效。核心决策先选后端再按后端收凭据v1 客户端的构造顺序被重排为先选后端再只接受该后端的凭据类型且凭据不进入ClientConfig。这包含四个子决策。直接构造的签名与双凭据类型v1 直接构造签名在概念上为NotebookLMClient( credential: AuthTokens | AndroidMasterToken, *, config: ClientConfig | None None, )两种凭据类型的字段与不变量凭据类型定位关键字段不变量AuthTokensWeb 引导凭据取 ADR-0032 的冻结目的地形态initial_cookies、初始csrf_token与session_id、authuser、account_email、可选storage_pathinitial_cookies只被一次性拷贝进 Web 运行时所有权它不是活的 cookie jar对象不会被 refresh 变更AndroidMasterToken公共冻结、脱敏的直接凭据email、android_id以及一个秘密 master-token 值秘密值排除在 repr、错误、日志、指标与序列化辅助函数之外它是内部MasterToken值的公共投影实现必须保持唯一权威值不得引入第二套独立的 token 模型仓库源码已经为后者预留了权威的内部形态。内部 MasterToken 值对象正是 frozen dataclass 加脱敏 reprdataclass(frozenTrue, reprFalse) class MasterToken: An immutable master-token credential with a redacted secret. email: str android_id: str secret: str field(reprFalse) def __repr__(self) - str: return ( fMasterToken(email{self.email!r}, android_id{self.android_id!r}, secretredacted) )ADR-0039 要求公共的AndroidMasterToken成为这个内部值的“公共投影”与源码中“实现必须保持一个权威值”的约束完全对应。后端解析优先级与失配即同步报错后端的选择顺序是一个冻结的偏好链ClientConfig.backend显式配置环境变量NOTEBOOKLM_BACKEND默认值web。凭据永远不能隐式选择或覆盖后端。两条硬约束Web 后端收到AndroidMasterToken或 Android 后端收到AuthTokens必须在同步地、早于任何文件系统访问、可选依赖检查、凭据获取或网络 I/O 之前抛出ConfigurationErrorAndroid 不接受空造的或伪造的AuthTokens占位符。从源码结构看这条偏好链已经存在于当前代码中client.py 的 from_storage 先用config.backend.kind若有或backend参数得到显式值再回退os.environ.get(NOTEBOOKLM_BACKEND)而 _app/client_config.py 的 adapter_client_config 则展示了后端解析后按结果装配不同ClientConfig属主AndroidBackendConfig或WebBackendConfig的分派模式——ADR-0039 把这个模式提升为公共边界契约。存储构造from_storage 的凭据优先级NotebookLMClient.from_storage(path, profile, allow_headless, *, config...)保持是标准工厂且必须在解析或加载任何凭据源之前用同一冻结偏好链解析后端。两个后端的优先级各自独立Web 的优先级保持现状显式pathNOTEBOOKLM_AUTH_JSON的存在性——包括空值或畸形值它们会作为 Web 输入失败而不是继续向下回退显式、环境变量选择、激活或默认 profile。Android 的优先级显式path被解释为 profile storage-state 位置其同级的master_token.json才是真正的凭据storage-state 文件本身可以不存在显式、环境变量选择、激活或默认 profile 及其名义上的master_token.json。Android忽略NOTEBOOKLM_AUTH_JSON且不解析它因为那个值不是 Android 凭据。两条横切规则显式path对两个后端都压过profileprofile可以保留为上下文元数据但不能重定向凭据读取Android 的路径解析必须从名义 profile 目录出发不能允许旧的 cookie 文件回退把 master token 查找带偏。直接凭据因为“已解析”而压过一切存储源。直接 WebAuthTokens.storage_path只选择其持久化目标直接 Android token 留在内存中除非另有显式的持久化操作写盘——构造过程永远不会静默存储一个持久账号凭据。仓库中 master token 的存储布局与这一契约吻合。master_token.py 的持久化注释明确写着 master token 以 mode 0600 存储在storage_state.json旁边“beside storage_state.json”读写都委托给 storage 模块 的原子写路径_atomic_io原子替换 fsync 持久 临时文件清理且在受限的同级锁下执行。磁盘上的记录格式见 legacy record 编解码{ version: 1, email: ..., android_id: ..., master_token: ..., # 秘密值0600 落盘 }解析器对version与三个必填键master_token/email/android_id任一缺失都会以脱敏的MasterTokenError报“malformed or an unsupported version”不泄露内容。现有的ProfileStore事务语义保持权威规范路径与 profile 锁只解析一次写操作走既有的跨进程锁、原子替换、权限、快照/CAS 规则与账号路由检查。ADR-0039 改变的只是后端分派选择哪个加载器运行不改变磁盘格式或事务保证。缺失、不可读、畸形、错账号这四类失败源必须保持可区分且不含秘密。公共身份与刷新契约client.auth变成“选定后端的视图”Web返回不可变的 WebAuthTokens引导值。可变活 cookies、CSRF/session 替换、持久化基线、generation/CAS 状态全部留给 Web 属主私有Android返回冻结、无秘密的AndroidAuth视图仅含权威的 master tokenemail与android_id永不暴露持久 master token 秘密或任何 Web 字段。这一条在 C9b 切点终结了 ADR-0016 的可变AuthTokens身份不变量ADR-0016 的 logger 命名决策不变需要账号身份的代码一律改用get_account_email()而不是读后端特定的凭据字段。各方法的跨后端契约方法 / 属性WebAndroidget_account_email()身份已知时无网络、后端中立master token 的 email 在凭据加载后即为权威过期的 Web cookie/profile 账号元数据无关get_account_authuser()保留与当前 Web 账号状态配对的路由仅 Web 可用在 I/O 前抛公共UnsupportedOperationErrorrefresh_auth(*, allow_headlessFalse)刷新活 cookie / 请求令牌属主并经现有事务边界持久化从选定 master token 作废并重铸 bearer全程无 Web I/Orefresh_auth(allow_headlessTrue)Web 专属的恢复请求抛UnsupportedOperationError直接构造的客户端可以在 open 之前报告凭据身份存储客户端在工厂加载完凭据后才存在因此一经返回就已有相同身份。刷新既不替换也不泄露 Android 持久凭据。当前源码中 Android 刷新路径已经体现了“bearer 重铸与 Web 解耦”的骨架client.py 的 _refresh_auth_for_epoch 在preferred android时调用android.bearer_provider.refresh(expected_epoch)之后仅当 Web 兼容 sidecar已经物化时才 best-effort 刷新其 cookie且 sidecar 刷新失败只降级为 WARNING、不会把一次成功的 bearer 刷新变成公共失败——这与 ADR-0039 “Android 刷新无 Web I/O”的契约方向一致。兼容性约束与 C9b 发布门禁C9b 的删除清单移除根rpc_call()包装器与LazyWebSidecar。删除之后类型化的 Android 操作、import、构造、open、refresh、close以及 CLI、MCP、REST 入口必须做到“四不”不加载任何 Web 实现模块、不分配任何 Web 对象、不解析任何 Web 内联凭据、不发起任何 Web 请求。发布切点同时完成 ADR-0032 计划内的AuthTokens移除、弃用的公共行解码器、awaitable 存储工厂、扁平调参参数、polling/confirmation 迁移以及完整 P8 登记簿中的其余所有行。三条纪律性约束API 审计豁免只适用于审计实际报告的断裂纯行为变更留在 v1 runway 登记簿与行为测试中任何 C9b 变更不得借用其他登记行的告警日期且源码注释或未发布的登记簿条目都不算已发货的告警。仓库的 deprecations 文档 中可以看到 C9b 门禁账本的实际形态账本要求“包含该告警的稳定 v0.9.0 必须先发布且 C9b 不能与告警同版”并在发布前要求重新执行远端 release/tag/source 审计——这正是 ADR-0039 所说的“ADR 接受本身不能授权这些断裂”的制度落地。后果收益与新增迁移成本收益侧来自 ADR 原文 Consequences 节Android 可以仅从master_token.json构造并打开无 cookie 文件、无主页请求、无 Web 可选依赖、无伪造 Web 凭据凭据失配失败前移到一个确定性的 I/O 前边界Web 保留已接受的 ADR-0032 引导模型ClientConfig因为只装策略、不装秘密而保持可安全检视。成本侧公共面新增AndroidMasterToken与AndroidAuth并改变构造函数、client.auth、refresh_auth()与 Androidget_account_authuser()的契约——这些是真实的 API 或行为迁移需要在账本中记录自己的告警证据。此外后端特定视图意味着需要检视原始认证细节的调用方必须按选定后端分支后端中立的账号 email 方法是稳定的身份缝seam直接 Android 调用方持有自己提供的持久秘密而存储认证的调用方无法通过客户端取回该秘密。被否决的五个替代方案及理由ADR-0039 明确记录了被否决的替代设计其拒绝理由值得借鉴继续要求AuthTokens为 Android 造空的 Web 字段——保留了假的凭据类型使非法状态可被构造且让 Android 引导继续耦合在 Web 的失败时序上。把凭据放进ClientConfig——配置是冻结的、可检视的策略把 cookie 或 master token 秘密放进去会混淆优先级、持久化、诊断与脱敏四类职责。让凭据自己选择后端——后端偏好本来就是独立的 显式/环境/默认 决策隐式选择会让配置错误静默地改变传输且无法达成干净的“I/O 前失配”契约。通过client.auth暴露存储的 Android master token 秘密——master token 是账号等价物级的持久凭据身份对调用方有用秘密取回对客户端运行不必要只会扩大泄露面。用单一公共可变凭据提供者统一 Web cookie 与 Android bearer 刷新——两个后端的凭据层级、存储、线上生命周期与安全影响都不同中性的生命周期协调不需要一个通用的公共 auth 对象。小结与延伸阅读ADR-0039 的本质是把“凭据”从一个跨后端统一的公共可变对象拆成两条互不越界的公共面Web 的不可变引导值 Web 运行时私有状态Android 的冻结脱敏凭据 私有持久秘密并用一条冻结的后端偏好链和“I/O 前失配即ConfigurationError”的契约把类型错位的失败从运行期网络路径推回构造期。它同时展示了 notebooklm-py 的发布工程实践设计接受ADR与破坏性变更生效C9b 门禁是两条独立的轨道。进一步可在仓库中对照阅读ADR-0039 原文含 Status 与全部决策条文ADR 索引查找 ADR-0016、ADR-0023、ADR-0032、ADR-0033、ADR-0034 等前置决策master token 模块oauth token 交换、cookie 铸发、0600 持久化与冷启动引导事务master token 类型与记录编解码MasterToken值对象、MasterTokenError与 version-1 记录格式客户端工厂与刷新路径from_storage后端偏好解析、refresh_auth双后端分派后端配置解析NOTEBOOKLM_BACKEND的默认链与ClientConfig属主装配发布与弃用账本C9b 门禁、告警窗口规则与 P8 登记簿。【免费下载链接】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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进