v1: Web vs Android credential types, frozen AuthTokens, and from_storage loader split
Parent: #2376. Replaces #2181 and #2182 as implementation specs.
#2182 wanted a public ClientAuth / ClientAuthSnapshot and refresh_auth() -> ClientAuth. Do not implement that. The accepted destination is ADR-0039.
What 0.x does today
NotebookLMClient(auth: AuthTokens, ...)always takes a mutable Web-shaped token object (cookies,cookie_jar,csrf_token,session_id, …), even whenbackend="android".from_storage(backend="android")still loads Web cookies and GETs the NotebookLM homepage (heal_psidts=False,poke=False) so deprecatedrpc_callcan later materialise a Web sidecar with populated CSRF/session fields.client.authis that same mutableAuthTokensinstance (ADR-0016 identity invariant). Refresh mutates it in place and successfulrefresh_auth()returns it.get_account_authuser()is implemented on both backends.- Android typed namespaces do not use the Web session; only construction + the deprecated hatch do.
v1 public contract
NotebookLMClient(
credential: AuthTokens | AndroidMasterToken,
*,
config: ClientConfig | None = None,
)Web AuthTokens (ADR-0032 Phase B) is a frozen bootstrap value, copied once into Web runtime ownership:
initial_cookies: CookieJar— immutable ordered sequence ofCookierows, not a live jar, not aMappingcsrf_token: strsession_id: strauthuser: intaccount_email: str | Nonestorage_path: Path | None— persistence target only, not a credential source
Construction is pure: no file or network I/O. Custom repr redacts cookies, CSRF, and session IDs.
AndroidMasterToken is a public frozen credential: email, android_id, and a secret master-token value excluded from repr, errors, logs, metrics, and serialization. It is the public projection of the existing internal MasterToken — one canonical value, not a second model. Construction never silently writes it to disk.
Backend selection is independent: ClientConfig.backend, then NOTEBOOKLM_BACKEND, then "web". A credential never selects or overrides the backend. Web + AndroidMasterToken, or Android + AuthTokens (including empty/placeholder tokens), raises ConfigurationError synchronously before filesystem access, optional-dependency checks, credential acquisition, or network I/O.
from_storage
NotebookLMClient.from_storage(path, profile, allow_headless, *, config=...) stays the canonical factory. It resolves the backend before parsing or loading any credential.
Web precedence:
- explicit
path - presence of
NOTEBOOKLM_AUTH_JSON(empty/malformed still fails as Web input; no fall-through) - explicit / env / active / default profile
Android precedence:
- explicit
pathas the profilestorage_state.jsonlocation whose sibling ismaster_token.json(the cookie file need not exist) - explicit / env / active / default profile and its nominal
master_token.json
Android ignores NOTEBOOKLM_AUTH_JSON without parsing it. Explicit path wins over profile for both backends. Path resolution starts at the nominal profile directory; a legacy cookie-file fallback must not redirect master-token lookup.
Also remove the pre-profiles home-root fallback (~/.notebooklm/storage_state.json outside profiles/<name>/) as part of the same v1 drain (warned since v0.8.1; listed on #2376). Preserve the relative order of the remaining sources.
ProfileStore transaction semantics stay: one canonical path, inter-process locks, atomic replace, 0o600/0o700, snapshot/CAS, account-route checks. Backend dispatch changes which loader runs, not on-disk formats. Missing, unreadable, malformed, and wrong-account errors stay distinguishable and secret-free.
client.auth and refresh
- Web:
client.authis the immutable bootstrapAuthTokens. Live cookies, CSRF/session replacements, persistence baselines, and CAS/generation state stay private to Web owners (Kernel, session auth,CookiePersistence). - Android:
client.authis frozen secret-freeAndroidAuthwith master-tokenemailandandroid_idonly. No durable secret, no Web CSRF/cookies/authuser. - Account identity for callers:
get_account_email()(backend-neutral, network-free when already known). Android email comes from the master token, not stale Web cookie metadata. get_account_authuser()is Web-only. Android raisesUnsupportedOperationErrorbefore I/O.refresh_auth(*, allow_headless=False) -> Noneon both backends. Web refreshes live cookie/request-token owners through existing transactions. Android invalidates and re-mints the gRPC bearer from the selected master token with no Web I/O.allow_headless=Trueis Web-only (UnsupportedOperationErroron Android). Refresh neither replaces nor reveals the Android durable credential.
This ends ADR-0016’s mutable-AuthTokens aliasing rule. The CORE_LOGGER_NAME == "notebooklm._core" decision is unchanged.
AuthTokens members to delete on the same cut
| Member | 0.x status |
|---|---|
from_storage(...) |
warned since v0.8.1 |
sync storage_path + cookie_jar=None construction |
warned since v0.8.1 |
flat_cookies |
warned since v0.8.1 |
replace_cookie_jar() |
source warning; not in a published release yet |
cookies, cookie_jar |
docs-only deprecation since v0.8.1 (dataclass fields cannot warn) |
cookie_snapshot |
docs-only; source since=0.9.0 unshipped |
jar |
warning-free 0.x migration shape; docs-scheduled v1 removal |
cookie_header / cookie_header_for |
docs-only |
Publish/API-pin Cookie and CookieJar as notebooklm.Cookie / notebooklm.CookieJar so AuthTokens(initial_cookies=...) needs no private import. Do not publish them accidentally via notebooklm.auth.__all__ unless that is an explicit extra decision.
Also publish AndroidMasterToken and AndroidAuth.
0.x until this cut
Keep mutable AuthTokens and the documented Android homepage bootstrap. Do not infer an Android credential mode from a missing, empty, malformed, or unreadable Web storage file. Those are distinct failures, not “master-token-only.”
Acceptance
- Clean interpreter: Android import,
NotebookLMClient(AndroidMasterToken(...))/from_storage(..., config=ClientConfig(backend=AndroidBackendConfig())), open,refresh_auth(), and typed namespace calls with zeronotebooklm._web*imports and zero homepage/batchexecute I/O - Web
from_storage/ directAuthTokensstill bootstrap cookies + CSRF/session correctly - Credential/backend mismatch raises
ConfigurationErrorbefore I/O client.authidentity andrefresh_auth() -> Nonematch ADR-0039 on both backends- Public
__all__, API-compat allowlist (every field/method the audit reports), behavioral runway for breaks the audit cannot see, ADRs,docs/deprecations.md(move rows to Removed), changelog, examples --check-stalerejects allowances with no matchingApiBreak(sync storage fallback, home-root fallback, sidecar class are behavioral/private)
Explicitly not this issue
- Public
ClientAuth/ClientAuthSnapshot/LiveAuthState - Returning
client.authfromrefresh_auth() CookieJaras aMapping(rejected; sequence of full-fidelity rows)- Putting credentials on
ClientConfig - Letting the credential object choose the backend
- Exposing the stored Android master-token secret through
client.auth - Removing
rpc_call(that is #2378; same release, separate PR surface) - Flipping artifact-absence / bound-settings / mind-map / prefetch defaults (#2379)
Source: teng-lin/notebooklm-py