The design record for the round after STORAGE-EGRESS.md: the engine's third provider strategy, the worker host's second bindable one, and the first with NO SHARING TIER by design. It is also the round that BUILDS the OAuth shape the egress record parked as v2 — the worker-run token exchange — because Drive is bearer-based and a bearer must never exist in page memory or cross the RPC port. Rulings settled 2026-08-22; authorities: issue #7 (authority in the instance), the egress record's §§2/5/6 (what may cross the port, the bearer ban, the escrow tiers), providers/dropbox/store (the strategy template), engine.wit's storage section. Where this document and reality disagree, report the friction, do not edit around it.
gdrive is a guest provider crate statically linked into the engine
composite (a store-config arm and six match sites; no wac change),
modeled on Dropbox's folder-shaped strategy with the entire link
machinery removed and S3's name-keyed object naming in place of
Dropbox's plain paths: every call runs Route::Owner, and the grant an
embedder derives for it is owner = the API origin, public = ∅,
shared = ∅ — the unused tiers are EMPTY, refusing by construction. The
OAuth ceremony splits across the port along capability lines: the PAGE
owns the popup (a window is a page capability; the consent renders in
provider pixels), the WORKER owns the verifier, the exchange, and the
tokens — which are born in worker memory, rest DEK-sealed in the device
namespace, and are refreshed behind the owner seam with the new token
re-sealed. The solo page's storage sheet grows a provider choice; the
demo page deliberately does not (its storage theatre is the sharing
story, and this provider's point is that there is none).
- The strategy mints NOTHING a non-credentialed party could use: no
shared links, no anonymous reads, no app-auth tier. The only readers
of the store are holders of the user's own OAuth. "No sharing" is
therefore structural, in exactly #7's sense:
store-public-fetchandstore-shared-fetchare wired over empty origin sets, and the guest's gdrive paths never call them. store-grant(doc, member)still writes the member's pickup object at a derivable path — the pull layer's key bootstrap, which the ACCOUNT'S OWN DEVICES need (each device is its own agent with its own prekeys) — and returns NONE: there is no link to carry because there is nothing a link could grant. (Recorded fact, UPDATED once names became keyed (§2): this record WAS write-only, because the owner-tier pull derived its device set from the doc folder's own listing. Keyed names make that listing unparseable, so the pickup is now the only bootstrap — it carries the name-key keychain and the device set, the same pair S3's K_p carries, and the pull cannot start without it. The clause that predicted this — "it becomes load-bearing the moment a pull path exists that cannot list the folder" — came due by way of a pull that can list the folder and learn nothing from it. Note the keychain is a SECRET but still not a CAPABILITY: knowing a name lets you derive where an object sits, never read it, because every read on this provider goes through the owner seam's OAuth. AMENDED 2026-08-23 (#93): the keychain is also CHECKPOINTED state now — it is minted inside the engine and no embedder can hand it back, so it rides the sealed state root with the keyhive archive. Until that change a page reload was enough to lose it and write a second complete copy of the store under a fresh doc-folder name; devstore row 41 asserts exactly one doc folder across a kill.)store-revokedeletes the pickup and returns the honest note: this store never minted a capability, so there is nothing server-side to revoke; a party holding the user's own Drive credential is outside this store's reach, and credential rotation at Google is the real lever. It does NOT rotate the name-key epoch, and the difference from S3 — whose revoke does — is deliberate: on S3, names ARE the read tier (the bucket serves unsigned public GETs, so a name is sufficient authority), and a fresh epoch is the hard forward boundary that stops a revoked holder of the old key from reading new writes. Here there is no anonymous tier at all, so a name is authority for nothing and rotating it would be ceremony with no boundary behind it. The reading side still walks a keychain newest-first exactly as S3's does, so a future revision that grows a reason to rotate needs only the trigger. (A consequence that follows from grant-returns-none and is accepted: the engine's pickup-link table stays empty on this provider, so revoking a member who was never granted deletes nothing and succeeds quietly. A louder refusal would need a grantee list the provider deliberately does not keep.)bucket-pullis owner-tier only. A call carrying apickuplink is refused by name — a link-tier pull is not a thing this provider has.
- Scope is
drive.filein the VISIBLE space — files created by this app only — anddrive.appdatain the hidden one, which is narrower still and is the default (§5b).drive.fileis the minimal honest scope for a folder a user can see, and it carries a fact worth stating twice: the confinement is PER CLIENT ID, so every device of an account must use the SAME client id or they cannot see each other's store. The client id is part of the store's identity, beside the root folder name. - Drive is id-addressed and names are not unique. The strategy resolves
name→id with
files.listqueries scoped to a parent folder, caches folder ids in instance memory, and never creates duplicates itself (upload is list-then-create-or-update: multipart create when absent, media PATCH when present). The folder-id CACHE is still instance memory and deliberately so — ids are public addressing, re-resolvable from any instance in onefiles.list. (WAS TRUE UNTIL 2026-08-23: the name-KEY that decides what those names ARE was instance memory too, which meant a respawned worker resolved nothing and created a whole duplicate tree under fresh names. Bucket state is checkpointed since #93; the caveat now applies to the id cache alone, where it costs a re-listing and nothing else.) - Layout mirrors Dropbox's SHAPE with S3's NAMES: root folder (the
binding's
root) → adocsfolder holding one folder per document, and a flatpickupfolder holding per-member bootstrap objects. Doc folder names and their children's names are KEYED —hex(HMAC( name-key, kind ‖ id)), the S3 provider's construction verbatim — so the fixed wordsdocsandpickupare the only labels an observer reads. The doc folder is keyed under the doc's FOUNDING name-key (epoch 0) rather than the current one, because a Drive folder is a container and a container that renamed itself on an epoch boundary would strand everything inside it; the objects within stay per-epoch exactly as S3's are. - The reason names are keyed here rather than left plain like Dropbox's:
contents are keyhive ciphertext already, so names were the remaining
disclosure, and doc ids have the two properties that hurt. They are
GLOBAL — the same shared document carries the same id in every
member's store, so anyone who can list two accounts learns from the
names alone that those accounts share a document — and STABLE, so
activity on one document is trackable indefinitely. This provider's
threat surface is metadata-only by construction (
drive.fileplus ciphertext), which is exactly why the metadata was worth spending a derivation on. - THE PICKUP OBJECT IS THE ONE UNKEYED LOCATION, and the exception is
what makes the rest possible: it is where a member LEARNS the
keychain, so a member who does not hold the keychain yet must still be
able to find it. Its name mirrors S3's
kp_location—hex(SHA-256("kp" ‖ doc ‖ owner ‖ member))— and it sits FLAT underpickup, with no per-doc subfolder, because any unkeyed per-doc folder name (the doc id plain, or hashed) would be a global, stable per-document label in every member's store: the precise disclosure the keying removes. What it costs is S3's cost, unchanged: a party who already knows the whole (doc, owner, member) triple can confirm the object exists. Existence privacy wants a pairwise-secret location and is the same #19/#10 item for both providers. - CONSEQUENCE FOR THE PULL, recorded because it moved a load-bearing
part: the owner-tier pull used to derive its device set by parsing
oplog-<hex>off the doc folder's own listing. Keyed names make that listing opaque, so the device set and the keychain now arrive inside the sealed pickup object, exactly as S3's K_p delivers them. The bootstrap record described in §1 is therefore no longer write-only. gdrive-configis{ root, api-base, space }— ADDRESSING ONLY, like every other arm (spaceis §5b's, and the guest refuses an unknown value by name).api-basedefaults tohttps://www.googleapis.comand exists for the same reason S3's endpoint is config: a self-hosted (or fake) backend is ordinary addressing, not a probe hack. The client id is NOT guest config — auth is entirely the seam's, and the guest has no use for an app identity it must never wield itself.
The v2 shape from the egress record's §5, now built:
oauthStart(spec)(host RPC): the worker mints the PKCE verifier, challenge and state — held in worker memory, one pending ceremony at a time — and returns the authorization URL. The page opens the popup on it; the popup lands back on the page URL with?code&state, and the page relays both tooauthComplete(code, state).- THE CODE MAY CROSS THE PORT, and the ruling says why: it is a one-shot artifact, bound to a verifier that never left the worker, consumed within the ceremony. It is not a standing credential; the bearer ban is about standing credentials.
oauthCompleteverifies the state, exchanges code + verifier (+ client id/secret) at the token endpoint — the WORKER's own fetch — and seals{access, refresh, clientId, clientSecret}into the namespace. Binding remainsbindStore's job; consent and commitment stay two acts.- CLIENT ID/SECRET CLASSIFICATION: installed-app identifiers. Google's own documentation says an installed app's client secret is "not treated as a secret"; the pair identifies the APP, gates nothing without the user's consent, and is the same class as the Dropbox appKey/appSecret the demo's grant already carries in page memory. It may cross the port as data and rest sealed beside the tokens.
- THE TEST PAIR IS PICKED UP, NEVER SHIPPED: rclone's published desktop
client (id
202264815644.apps.googleusercontent.com; secret published in their source under their own reversible obscure scheme — AES-CTR under a fixed committed key, whichrclone revealemits) is the candidate. PROBED LIVE 2026-08-23 AND ALIVE, by a method worth recording because the pair is expected to lapse: the token endpoint answersinvalid_grant(400) for this pair with a deliberately bogus code — meaning the client authenticated and only the code failed — againstinvalid_client(401) for a wrong secret or an unknown id, which is the control that makes the probe non-vacuous. The authorize endpoint separately renders a sign-in page rather than an error, which is where Google validates the REDIRECT URI: a loopback URI WITH A PATH (http://127.0.0.1:8600/solo.html) is accepted for a desktop client. Neither probe can say whether consent completes and issues tokens — that needs a real account and is the operator's live beat. rclone's own docs say the shared id is being retired DURING 2026, so it is expiring rather than stable; re-run the probe before trusting it. - WHEN IT LAPSES, MINT ONE — do not hunt for another published pair.
googleworkspace/cli(gws) was examined for one and deliberately ships none (itsoauth_config.rshits are a format doc-comment and test fixtures): its model is bring-your-own, andgws auth setupautomates exactly the console ceremony this fallback needs — it creates a Cloud project, mints a Desktop-app OAuth client and enables APIs, leaving the pair in~/.config/gws/client_secret.json. Either path lands in the same two sheet fields. Testing-mode clients carry three operator obligations that are not our bugs: add yourself as a test user (else "Access blocked"), enable the Drive API on the project (elseaccessNotConfigured403s), and click through "Google hasn't verified this app". If a Desktop client ever refuses our redirect, a Web-application client with the page URL registered verbatim is the shape to use. - Nothing is baked into source, bundles, or defaults; the fields are
typed (or URL-param prefilled —
gdclient/gdsecret/gdroot) at test time. Baking the borrowed pair in was considered and dropped in favour of the params, which already existed and cost nothing. - THE LIVE BEAT IS LOCAL, NECESSARILY — measured 2026-08-23, not
assumed. A desktop-type client accepts LOOPBACK redirects only: the
authorize endpoint renders a sign-in page for
http://127.0.0.1:8600/solo.htmland answersredirect_uri_mismatchfor the deployed Pages URL, with the same client id. So the borrowed pair cannot drive the ceremony from the public site by any delivery mechanism, and the two errands are separate: the Pages deploy ships the page, the Drive beat happens at a loopback origin. The deployed site needs a WEB-application client registered with its exact URL — which is the same client that replaces the borrowed one, so the replacement and the public Drive path arrive together.
- The sealed oauth row is DEVICE-scoped — deliberately unlike the SigV4 escrow, which is origin-shared. There is no platform handle for a bearer, so the DEK seal is the best rest available (honest tier: as strong as the device's rung, weaker than the SigV4 handle, which never exists as bytes after escrow). Sharing a bearer across devices would be credential sharing between agents; each device runs its own consent ceremony instead. Multi-device = same client id + same root, separate consents.
- The owner seam's 401→refresh→retry (the Dropbox shape, generalized per provider: Google's exchange includes the client secret) refreshes behind the seam; the refreshed access token — and a rotated refresh token, when Google issues one — is RE-SEALED through the grant callback, so a worker respawn resumes on the newest tokens.
- Reseal: the in-memory bearer drops with the grant and the DEK
(
clearGrant, the existing mechanism); the sealed row rests sealed and returns at unseal, whenbringUpEnginere-arms the grant and re-appliesinitStore. unbindStorekeeps the oauth row, exactly as it keeps the SigV4 escrow: forgetting the destination is not forgetting the account. A separate FORGET ceremony (forgetOauth) deletes the row and best-effort revokes the token at Google — the honest disconnect, and the only place revocation belongs.
StoreBindinggains{kind:"gdrive", root, apiBase, clientId}— addressing plus app identifiers, still nothing user-secret.bindStorerefuses: sealed (no-rung); empty root/clientId or an unusable apiBase (bad-destination); no sealed consent, or a sealed consent whose clientId differs from the binding's (no-credential— the access-key-mismatch rule's exact analog: fail at bind, never as a provider 403 later).- Grant derivation stays derived-never-accepted: owner = {origin(apiBase)}, public = shared = ∅, signer = the refusing one (no SigV4 on this provider).
DeviceStatusreports the binding as it does S3's, and growsgdriveConsent: {space} | null— null while sealed or absent, so the sheet can offer bind-without-ceremony when a consent already rests sealed. IT IS A NULLABLE RECORD RATHER THAN A BOOLEAN, mirroringstorage: StoreBinding | nullbeside it and for the same reason: a boolean plus a separate space field is two facts that can disagree, and one of them would eventually be read without the other. The space is addressing, not a secret; nothing else derived from the sealed row ever appears here.
- The binding's gdrive arm carries
space: "appdata" | "drive", and the guest'sgdrive-configcarries the same string, which it validates and refuses by name. It is ADDRESSING, likerootandapiBase: it picks the root folder's parent (appDataFoldervsroot) and addsspaces=appDataFolderto list queries, and everything below the root folder — thedocs/pickuplayout, the keyed names, the pickup construction — is identical between the two. This is a location choice, not a second strategy. - APPDATA IS THE DEFAULT, and the reasons are the ones a user cannot give themselves: the hidden per-app space CANNOT BE SHARED at all, so §1's "no sharing" becomes platform-enforced rather than a property this strategy promises about itself; the Drive UI offers no rename or move for those files, which matters here more than usual, because a store addressed by KEYED NAME (§2) cannot find a file a user renamed — the visible-folder version of that mistake strands data permanently; and it keeps a folder of meaningless hex out of the user's own Drive, which is the thing they would otherwise have to look at forever. Drive's settings still offer the one bulk lever that matters — remove all of this app's hidden data — so "hidden" is not "unremovable".
- VISIBLE STAYS ON OFFER, and not as a courtesy. Appdata cannot be INSPECTED by its own owner: there is no way to look at it, count it, or confirm with your own eyes that the thing you were told is happening is happening. It is also orphaned INVISIBLY by an app/client rotation — a new client id sees an empty app-data space and the old one's contents are unreachable AND unseeable, where the same rotation over a visible folder leaves a folder the user can still find. And the manual live beat against real Google (see Gates) is only checkable by eye in a visible folder. Inspectability is a real property; the ruling is that it is not the DEFAULT one.
- THE SPACE IS COUPLED TO THE CONSENT, so it rides on
OauthStartSpectoo. The space selects the SCOPE:appdataasks fordrive.appdata,driveasks fordrive.file. Those are two different permissions on two different consent screens, so choosing a space is a consent-time decision and not merely a bind-time one — and the narrowing is a benefit in its own right, sincedrive.appdatacannot reach anything in the user's Drive at all, not even files this app made there.bindStoretherefore refuses a binding whose space differs from the sealed consent's withno-credential, the client-id mismatch's exact analog (§5): the consent granted was for a different permission, so this browser cannot act for that destination, and the message tells the user to run the consent again. The sheet's bind-without-ceremony skip is space-aware for the same reason — it reuses a sealed consent only when the space matches, and otherwise runs the ceremony and says why. - AN API FINDING FROM THE STRATEGY SIDE, recorded because it decides
the failure mode: a
files.listthat OMITSspaces=appDataFolderdoes not error — it returns an EMPTY LIST. On a list-then-create strategy that reads as "absent", so the store does not fail loudly; it FORKS, re-creating everything under a second root and diverging from the one that already exists. The fake reproduces this exactly (an empty list, never an error), which is what makes the space rows non-vacuous.
The solo sheet grows a provider choice (S3-compatible | Google Drive); the Drive form is chrome-owned fields — the space choice (§5b; hidden app data DEFAULT, visible folder the alternative, both described in the visor's own words including what each one costs, plus the one line that changing it later asks for a new consent because it is a different permission), root folder, client id, client secret (masked; it is an app identifier, but it is also not something to paint on a screen) — a Connect ceremony that runs consent → bind → activation beats, and the same sync/change/disconnect controls. The solo boot grows the popup-relay branch the demo page already has. The demo page does NOT grow a Drive panel: its storage theatre exists to show the sharing tiers, and a no-sharing provider adds nothing it can show. Recorded, not forgotten.
- Engine: clippy + wasm build + compose + translate; a new headless
bringup phase
gdrive— the full owner beat (initStore → ensureBucket → grant self → flush) plus a cold second engine pulling from the bucket alone, against an in-process fake Drive. The cold pull is also what proves the keyed naming did not cost findability: a second device reconstructs the whole document from a store whose every name it can only derive, never read. Existingsolo,resume,pairbatteries unchanged. - The fake (demo/host/fake-drive.ts, one module for bringup, the
devstore harness and e2e): minimal files API (
files.listwith the q-subset the strategy emits, multipart create, media update,alt=mediaget, folder metadata) plus the OAuth half —/auth302s straight back with a synthetic code (headless consent),/tokenverifies the PKCE verifier against the challenge it saw (OUR PKCE is what it gates), issues synthetic labeled tokens, supports refresh with rotation and on-demand expiry. - Devstore rows: ceremony tokens sealed and never in
status(); bind refusals by code (no consent; client-id mismatch; SPACE mismatch — a consent for one space cannot bind the other); an appdata bind landing its objects in the HIDDEN space and nowhere in the visible one (the isolation property proven through the worker, not only in bringup);status().gdriveConsentnaming the space it was granted for and reporting null while sealed; bind + flush with Bearer egress observed; kill/__die+ re-unseal with no re-ceremony (binding AND tokens survive sealed; initStore re-applied); 401→refresh→retry with the re-sealed token surviving a second kill; reseal/unseal; unbind keeps consent, forget deletes it (+revoke observed at the fake); and the naming regression row — after a flush, the structure still resolves (docs/pickup, a doc folder with objects in it) while the doc id's hex appears in NO stored name anywhere in the fake's tree, folders included. The account's storage record adds two more: a fresh account'sus-storage-getanswers an ABSENCE and never an error (the sheet asks it on every open and treats the answer as data); and a synthetic labeled gdrive record — client pair included — put through the RPC survives__die+ unseal with no second write, becauseusStoragePutis deliberately absent fromREADONLY_METHODSand therefore schedules a checkpoint like any other mutation. - e2e
solo-gdrive: the full ceremony headless through the real popup path (the fake's/authredirect), tokens appearing NOWHERE in page storage, objects landing in the fake's store, reload → sync with nothing re-entered, reseal seals it, forget revokes. - e2e
solo-account-storage: TWO independent solo devices in ONE account, against the same fake — the headline of "The account syncs its storage config; devices keep their credentials". B enrolls FIRST (so A's bind is a change to an account B already belongs to), A binds Drive through the full sheet ceremony and writes the record through, B ANNOUNCES the change on the visor's rule line while its own store is still unbound, B's sheet then leads with the account's destination and offers NO field for folder, client id or client secret — only a Connect button and the#storage-divergeescape hatch — and B binds with a consent click and nothing typed. Both devices' flushes land in ONE store: one root folder in the hidden space, and B's doc folders a superset of A's, never a disjoint fork. - Known coverage gap: the S3 ADOPT path — a second device meeting an
account whose record is the s3 arm, with the addressing prefilled and
settled and the secret-key escrow as the only input — has NO e2e
scenario yet. The gdrive adopt path is the covered one
(
solo-account-storage); the s3 half is exercised only by the sheet's own code being shared between the two arms, which is a weaker claim and is recorded here rather than implied to be tested. - Existing batteries stay green throughout: devstore matrix, e2e suite, invariants (nothing widens), pairing-bringup, resume-bringup, S3 acts.
- The live beat is manual and the operator's: serve locally, solo → storage → Google Drive, the picked-up pair (or a minted one — see §3 for both paths and the freshness probe), real consent, real flush; verify in drive.google.com that the app's folder exists and holds ciphertext objects. Documented here, not automated — a real Google account cannot and should not sit in CI.
The §3 classification — client id and secret as installed-app identifiers, public by nature — is Google's own, and it holds for DESKTOP clients. It does NOT transfer to the deployed site, and the difference is structural rather than a policy nuance:
- A desktop client accepts LOOPBACK redirects only (probed: sign-in page
for
http://127.0.0.1:8600/solo.html,redirect_uri_mismatchfor the Pages URL under the same client id). So the honest-secret client type is exactly the one that cannot serve a public page. - A public page therefore needs a WEB-APPLICATION client, whose secret
Google DOES treat as confidential — and the token endpoint demands it:
probed, an authorization-code exchange with PKCE and no
client_secretis refused outright (invalid_request: client_secret is missing). There is no secretless browser flow to fall back to.
The consequence, stated rather than discovered later: a browser-hosted Drive client cannot keep a confidential client secret, so the provider is honest at a loopback origin and, on a public origin, only in one of two postures — a TESTING-MODE app, where the test-user list is the real gate and the secret gates nothing beyond it, or behind a token-exchange broker that holds the secret, which this project does not have and which would put a server in the middle of a design whose whole point is that there isn't one. v1 ships the loopback posture and records the rest; a public Drive beat runs testing-mode with the operator as the only test user.
This is a fact about GOOGLE'S OAUTH, not about the worker-run ceremony: the worker still owns the verifier and the tokens, and the secret it holds is the app's identity, never the user's. What changes on a public origin is only how much that app identity is worth protecting.
The shipping posture for a served page is the user's OWN web-application client, minted by them and typed into the sheet — which is what the sheet already asks for, so this is a ruling rather than a feature. It is the SAME BARGAIN THE S3 PROVIDER MAKES and should be read beside it: bring your own bucket, bring your own keys, the visor escrows them. The Google secret then protects the user's own app identity, sits in the user's own browser, and names the user's own app on the consent screen — so the confidentiality Google expects becomes a promise the user is making to themselves, which is the only version of it a browser can keep.
The alternative Google offers browser apps — the TOKEN MODEL (implicit-style: an access token straight to the page, no secret, NO REFRESH TOKEN) — is recorded and rejected for v1 with its cost named: one-hour access and no unattended renewal means a device closed for an hour cannot sync until someone puts a foreground gesture in front of it, which is precisely the property the worker host exists to provide. It becomes the right answer only if zero-setup Drive ever outweighs background sync.
The boundary this draws for the sheet's own dev conveniences: the
client id may travel in a URL (?gdclient=) because it is a public
identifier, but the secret may not — a URL is synced browser history
plus a server request log, and it enters this sheet through the masked
field only.
Note that §3's classification is DROPBOX-SHAPED and does not generalize: Dropbox issues PKCE public clients, so the demo's owner seam refreshes with a client id and no secret at all. Google has no such client type for the code flow. A seam that grows a third OAuth backend should model "does this provider issue public clients" rather than assume either shape.
Ruled 2026-08-23 (owner's call, on the question "should BYO client credentials sync?"). The answer splits three ways, and the split is the bright line worth keeping legible:
- The DESTINATION and the CLIENT PAIR sync, through the account.
They are account-level configuration BY NATURE:
drive.fileconfines visibility per client id (§2), and the layout hangs off root + space, so every device MUST agree on all of them or the store forks — invisibly, in the appdata space. Config that all devices are required to agree on but each device types independently is a silent-fork failure mode with a UI: a second device typing a different-but-valid client id consents fine and then sees an EMPTY store. So the account's user-system doc carries ONE storage-config record (us-storage-put/us-storage-get, beside the partition pointer map and for the same reason it exists), written through when a device binds, announced on the others when it changes. The sync channel is keyhive E2E — the record is ciphertext on the wire, in the bucket, and in every checkpoint — and the client secret is APP identity that every device legitimately holds in cleartext anyway, so syncing it crosses no line that per-device sealing had not already crossed. This does not contradict the never-in-URLs rule: that is about UNTRUSTED channels (synced browser history, request logs); this is the trusted E2E channel. Different question, opposite answers, both right. - TOKENS AND CONSENT stay per-device, and not only on principle (standing user credentials never cross even trusted channels): Google treats a refresh token as one client session's, and concurrent use of one refresh token from several devices risks token-family invalidation — one device's refresh killing its siblings'. The consent popup is also the moment the user authorizes THIS device, and it costs one click.
- The SigV4 secret structurally cannot sync — it exists only as a non-extractable handle; there are no bytes to put in a document. The S3 arm of the synced record therefore carries addressing and the public access-key identifier only, and each device still escrows the secret itself. The hierarchy is the honest one: what CAN be a handle must be one (per-profile); what must be a string and is APP identity may ride the account's E2E state; what must be a string and is USER credential stays sealed per-device.
The sheet's consequence: on a device whose account already carries a
storage record, the storage sheet leads with "your account syncs
through …" and the remaining ceremony is exactly the per-device part —
the consent click (gdrive) or the secret-key escrow (s3). A bind that
changes the destination writes the record through, which is a change
the OTHER devices announce (us-events), never silently adopt.
Recovery inherits the right property for free: the user-system doc
rides the account, so a restored account knows where its bucket is —
and holds the client pair to reach it, lacking only this device's own
consent.
An opt-in token-exchange broker — holding the secret, so zero-setup Drive becomes possible — is a direction, not a plan. What the probes settled about its shape:
- A broker is PERMANENT in the token path, not one-time. Google
demands
client_secreton the REFRESH grant as well as the initial exchange (probed:client_secret is missingwithout it,invalid_grantwith it). There is no "use the broker once, then run independently". - The one shape where a broker never sees a token is client
assertions (
private_key_jwt): the broker signs a short-lived assertion and the worker presents it to Google itself. Google's support on USER-consent grants is UNPROVEN — a junk assertion producedinternal_failure(500) rather than the missing-secret refusal, so the parameter takes some branch, which is suggestive and nothing more. One experiment with a properly registered key settles it; nothing should be designed on it before that. - The trust at stake is smaller than it looks, and structurally so.
drive.fileconfines the token to app-created files, and those files are keyhive ciphertext by construction. A wholly malicious broker therefore gets availability (delete, withhold) and METADATA — never content. The storage credential is not a data-confidentiality credential in this design, and that is the sentence any broker's consent copy has to be able to say honestly. - Which made the metadata the lever — and the lever is now shorter.
This bullet used to say that plain derivable names were the gap and
that porting S3's name-key scheme was the change that would blind it.
That change is DONE (§2): doc folder names and every object name under
them are keyed hashes, and the one unkeyed location is the pickup
object, whose name is a hash of the (doc, owner, member) triple. So a
broker, or Drive itself, no longer sees doc ids or chunk ids in any
name. WHAT REMAINS VISIBLE, stated plainly because a shorter lever is
not no lever: the fixed container words
docsandpickup; the NUMBER of documents this account stores; per-document object counts and byte sizes; write and read TIMING; and the GROUPING itself — which objects belong together, since the folder still gathers one document's objects even though it is not labelled with which. Name-keys blind labels, not traffic shape. (One of those counts was WRONG rather than merely visible until 2026-08-23: the keychain was instance memory, so every respawn added another doc folder for the SAME document and the "number of documents" an observer saw was really a count of respawns. Bucket state is checkpointed since #93 and the folder count is now the document count.) Two further honest limits: a member who once held the keychain can go on deriving names (harmless here — a name is not a read, since every read needs the user's own OAuth), and an observer who already knows a (doc, owner, member) triple can confirm that pickup object's existence, which is the same existence- privacy gap S3 carries and the same #19/#10 item.
Drive in the demo page/picker; shared/team drives; resumable uploads (multipart only — chunks are small by construction); proactive token expiry (lazy 401 is the shape); a general OAuth-provider framework (this is one provider's ceremony, recorded concretely; abstract after the second one exists); Dropbox-on-worker (unchanged from the egress record — and now cheaper, since the exchange machinery exists).