# Google Drive changelog

What changed in each Google Drive simulator release, newest first.

What changed in each Google Drive simulator release, newest first.

## v0.0.8 — 2026-11-05

1. **A slow client no longer holds up the others.** The simulator reads a request's whole body before it starts serving it and sends the answer after, so a client that uploads slowly, or reads a large answer slowly, no longer delays other clients' requests. Answers are unchanged; one larger than 4 MiB still makes other requests wait while it is sent.

2. **Shared drives, download operations and batch requests.** `drives.create`, `drives.get`, `drives.list`, `drives.update`, `drives.delete`, `drives.hide` and `drives.unhide` serve shared drives: `drives.create` needs a `requestId` and answers 409 `teamDriveAlreadyExists` to one the same user already used on a drive that still exists, a drive's members hold `organizer`, `fileOrganizer`, `writer`, `commenter` or `reader`, `drives.get`, `drives.update`, `drives.hide` and `drives.unhide` answer 404 to a caller who is not a member, only an organizer renames or deletes a drive, and a drive that still holds an untrashed item answers 403 `cannotDeleteResourceWithChildren`. `files.create` places a file in a drive, and a permission on a drive's id adds a member. `POST /drive/v3/files/{fileId}/download` answers an operation that is already done, whose `downloadUri` fetches the bytes, and `GET /drive/v3/operations/{name}` reads it back for the user who started it. `POST /batch/drive/v3` runs each part of a `multipart/mixed` body as its own request and answers one response part per part, in order. `files.list` with `includeItemsFromAllDrives=true` and `changes.getStartPageToken` with `driveId` refuse a missing `supportsAllDrives` as Drive does, `changes.list` with `driveId` also refuses a missing `includeItemsFromAllDrives`, and the change routes with `driveId` answer 404 for an id that names no drive and 403 `teamDriveMembershipRequired` to a caller who is not a member.

3. **The call log shows what each call asked for and whether it succeeded.** Each `GET /_rystic/calls` entry now carries `args`, the call's query and body arguments with credentials redacted (long values cut, file contents left out), and `ok`, false for an answer that is not 2xx. A path the simulator does not serve is marked `unknown_route: true`. `?tail=N` returns only the last N calls, and `dropped` counts the calls the log no longer keeps.

4. **Coverage lists resumable uploads.** The docs' Coverage section now lists the resumable upload session routes under `/upload/drive/v3/files` and `/upload/drive/v3/files/{fileId}`, which the simulator already served, and counts them in its route total.

5. **The docs are one page, with a page per feature.** The archive ships `README.md` in place of `HOWTO.md`, as the standard simulator page, and `CHANGELOG.md` beside it. The change feed and shared drives each have their own page under `docs/features/`, as do uploads and downloads, and every example on these pages runs as written against a fresh simulator.

6. **The docs page links the admin token section.** The page now sends you to the admin token's section of the shared-simulators page.

7. **Domain-admin access and shared-drive roles.** `useDomainAdminAccess=true` is served on `drives.get`, `drives.list`, `drives.update` and `drives.delete` and on `permissions.list`, `permissions.create`, `permissions.get`, `permissions.update` and `permissions.delete` for a shared drive: it opens the drive and its member list to a domain administrator who is not a member, and never the drive's contents, so the administrator's `files.create` or upload into the drive answers 404 `notFound` and stores nothing. A caller who is not an administrator is refused or answered 404, as Drive does, and `allowItemDeletion=true` with the flag deletes a drive that still holds items. A seeded user marked `domainAdmin` is an administrator, and a new drive is visible to it at once, where Drive can take up to about 10 seconds. Any member of a shared drive can read one member's permission with `permissions.get` on the drive's id. A multipart upload into a shared drive, or into a folder inside one, now stores the file as one of the drive's items, where the simulator answered 500 or left it outside the drive. In a shared drive, trash, move and share follow the caller's role: a `reader` and a `commenter` are refused with 403 `insufficientFilePermissions`, a `writer` can share but neither move nor trash, and a `fileOrganizer` or `organizer` can do all three. As on Drive, `files.copy` without `supportsAllDrives=true` answers 404 on a file in a shared drive and `files.list` without `includeItemsFromAllDrives=true` leaves shared-drive files out. Right after a grant on a shared drive, Drive can refuse the new member's `comments.create` with 403 `insufficientFilePermissions` for up to about two minutes, while the simulator accepts it at once, so retry that 403 with backoff.

8. **Scenario descriptions use customer terms.** The Scenarios table and `--list-scenarios` describe each scenario in American spelling, with no internal references.

## v0.0.7 — 2026-10-31

1. **Seed large files without inlining their bytes.** A seeded revision can name its bytes as a file under the folder `RYSTIC_CONTENT_ROOT` names, `"content_ref": "file:<path>"`, in place of `body_b64`; downloads stream that file from disk without loading it into memory, byte ranges included, and a copy points at the same file instead of duplicating it. A seed naming a file that does not exist is refused.

## v0.0.6 — 2026-10-27

1. **Swagger UI to read and call the twin from the browser.** `/_rystic/swagger`, linked from the state UI header, is Swagger UI over the published API trimmed to the operations this twin serves; Try it out sends each request to the twin.

## v0.0.5 — 2026-10-14

1. **This version carries every change listed under v0.0.4.** v0.0.4 was withdrawn minutes after it shipped, when a check of its downloads hit a transient error, so no customer kept it. This version ships the same twin, including the change feed (`changes.getStartPageToken` and `changes.list`).

## v0.0.4 — 2026-10-14

1. **Resumable uploads.** `uploadType=resumable` on `POST /upload/drive/v3/files` and on the `PATCH` of an existing file answers a `Location` session URI, takes the bytes in chunks under `Content-Range` (308 with a `Range` header until the last one) or in one `PUT`, answers a status query, and creates or updates the file when the last byte arrives, so a Google client that switches to a resumable upload for a large file works. A `POST` with no `uploadType` is a media upload, as on Drive, and an unknown `uploadType` answers 400. A multipart upload takes its `mimeType` from the metadata, else the media part, else the name's extension, and stores a nameless file as `Untitled`.

2. **A bearer token must look like a Google access token.** The twin now accepts a bearer value only when it starts `ya29.`, the prefix every Google access token carries, and refuses any other as Drive refuses an invalid token: 401 `authError` with `WWW-Authenticate: Bearer … error="invalid_token"`. A client that sent a made-up token such as `test` must send one that starts `ya29.`, such as `ya29.placeholder`.

3. **More of Drive's refusals, as Drive words them.** A reader or commenter is refused a rename, an upload, a comment or reply edit, a reply or permission delete and a file in someone else's folder; a non-owner is refused a delete; a copy or download of a `copyRequiresWriterPermission` file by a reader or commenter answers 403 `cannotCopyFile` or `cannotDownloadFile`; a read-only-locked file refuses a rename and a comment; `size` or another read-only member in an update answers 403 `fieldNotWritable`; a folder moved into itself or its own child answers 400; a plain file named as a parent answers 403 `parentNotAFolder`. `addParents` without `removeParents` moves the file, a `mimeType` in an update is ignored, and a download ignores `fields`, as on Drive. Each answer was measured on Drive.

4. **An upload over 64 MiB names the cap.** A media or multipart upload over 64 MiB answers 413 `request body exceeds blob_cap_bytes 67108864`, the cap `/_rystic/version` reports, where it answered `request body too large`.

5. **A Google Doc's revision reads and updates as on Drive.** A Doc lists one revision, ID `1`, with its ten `exportLinks` and no `md5Checksum`, `size` or `keepForever`. Updating it answers 200, and deleting it answers 403 `revisionDeletionNotSupported`. Before, the list named a revision no other call could find, and a read or update of it answered 404.

6. **A deleted reply is scrubbed, and a plain reply reopens a resolved comment.** Reading a deleted reply with `includeDeleted=true` answers only `kind`, `id`, `createdTime`, `modifiedTime`, `htmlContent` "" and `deleted`, and its `modifiedTime` is when it was deleted. A reply without an `action` on a resolved comment carries `action: "reopen"` and reopens the comment, as on Drive.

7. **Docs and folders you create carry Drive's links and size.** A Google Doc made by `files.create` has `size` "1024", its ten `exportLinks` and a `webViewLink` on `docs.google.com/document/d/<id>/edit`. A folder's `webViewLink` is `drive.google.com/drive/folders/<id>`. The seeded Docs list all ten export formats.

8. **A change feed to poll.** `GET /drive/v3/changes/startPageToken` and `GET /drive/v3/changes` serve Drive's change feed: take a start token before listing with `files.list`, poll from it, read on with `nextPageToken` while a page carries one, and keep the last page's `newStartPageToken` for the next poll (`pageSize` 1 to 1000). Each file created, updated, uploaded to, moved, trashed, shared or unshared since the token is listed once, holding its state as the caller reads it now, and one the caller can no longer read is listed `removed: true`; each user's feed holds only what that user can read. The twin lists a change at once, where Drive lists it 1 to 4 s later, and `POST /_rystic/reset` restarts its tokens. `changes.watch`, `files.watch` and `channels.stop` are not served. The feed's known gaps, among them `fields`, the shared-drive parameters and a folder's trash, are listed with their tickets in the docs.

9. **The change feed lists what Drive lists.** A file someone stops sharing with you, or deletes, is now listed `removed: true` in your feed even if you found it through `files.list` or `files.get` rather than the feed. A token's answer no longer depends on which polls came before it: reading it again answers the same while nothing has changed since, and picks up later writes in their current state. A file you reach only through a link is never listed. Trashing or untrashing a folder lists every file below it. `fields` now narrows both changes routes: with no `fields`, each listed file holds only `kind`, `id`, `name` and `mimeType`; an empty `fields` answers `{}`; and a member a change lacks answers 400 `invalidParameter`. A new comment, a revision kept forever and a deleted revision now list their file, while a reply, even one that resolves its comment, lists nothing. The feed refuses `driveId`, `includeItemsFromAllDrives` and `spaces` as Drive does, `restrictToMyDrive=true` lists only the files you own, and a request with no credential names `DriveChanges.List` or `DriveChanges.GetStartPageToken` in its 401.

## v0.0.3 — 2026-10-13

1. **Seeded files and copies download their bytes, and a file carries only the members Drive gives it.** Every binary file in a shipped world downloads the bytes its `size` and `md5Checksum` describe (sizes are now tens of kilobytes), and a `files.copy` downloads its source's bytes under its own head revision and `createdTime`. A file no longer answers `""`, `{}` or `null` for a member Drive leaves out, so the Go client decodes every world's file list; a file made from metadata alone carries `size: "0"`, `md5Checksum`, `headRevisionId` and its links, owners carry an email, and trashing a My Drive file sets no `trashedTime`.

2. **A `files.list` page walk survives deletes, and no listing answers the root.** A `pageToken` keeps working after the files of an earlier page are deleted or trashed: the walk continues with the files still after it, as Drive's does. Each world's `My Drive` root folder is no longer a row of `files.list`; `files.get` on its ID still answers it.

3. **Ownership transfer works as Drive's.** An owner's `permissions.create` with `transferOwnership=true` makes the grantee the owner at once and leaves the old owner a writer, so `owners`, `ownedByMe` and `capabilities` follow; a writer's transfer answers 400 `invalidSharingRequest` "Sorry, you do not have permission to share."

4. **A read-only content restriction locks a file's content.** A file whose `contentRestrictions` hold `readOnly: true` answers `canModifyContent`, `canRename` and `canComment` false for every caller (`canEdit` stays true, as on Drive), a media update of it answers 403 `contentRestrictionViolation`, and `files.update` stores the restriction with its `type`, `restrictingUser` and `restrictionTime`. The Security Policy doc in `google-drive_team_workspace` now agrees with its restriction.

5. **Folders behave as a tree.** `files.update` with `addParents` and `removeParents` moves a file, and `'<folderId>' in parents` finds it under its new folder. A file inside a trashed folder reads `trashed: true` with `explicitlyTrashed: false`, drops out of `q=trashed = false`, and comes back when the folder is restored. The ID `root` names your `My Drive` root on every route that takes a file ID, and a file created without `parents`, by `files.create` or either upload type, lands in that folder under its real ID.

6. **`files.list` takes more of Drive's search terms.** `q` now takes `fullText contains` (whole words of the name, the description or a text file's content), `sharedWithMe`, `'me' in readers` and `in writers` (a role at or above it, inherited through folders), an email or permission ID in `owners`, `createdTime`, `modifiedTime <`, `!=` on `trashed` and `starred`, `appProperties has`, double-quoted values, `True`/`False` in any case, and `name contains` words in any order. Without `orderBy`, or with an empty one, files come newest `modifiedTime` first, and an empty sort key is skipped. `q=name < 'x'` still answers 400, where Drive answers 500.

7. **An upload holds up to 64 MiB.** A media or multipart upload, create or update, now stores and downloads byte for byte up to a 64 MiB request body, where it stopped at 1 MiB; a multipart body's metadata and part headers count toward it. A larger body answers 413 `request body too large`, and `/_rystic/version` reports the cap as `blob_cap_bytes`.

8. **`'root' in parents` lists My Drive's top level.** `files.list` reads `root` in `q`'s `parents` term as the caller's My Drive root, as every route that takes a file ID already does, so `q='root' in parents` answers the files and folders directly under it.

9. **Every user has a My Drive root.** `root` resolves for every user in every world, after `POST /_rystic/reset` too, and a file or folder created with no parents lands in it. A folder you create no longer becomes the root and drops out of `files.list`.

10. **A media upload onto a folder leaves it a folder.** `PATCH /upload/drive/v3/files/{folder}?uploadType=media` answers 200 with the folder's metadata, as Drive does. The twin writes nothing, where it turned the folder into a file; what Drive does with the uploaded bytes is unmeasured.

11. **JSON answers carry Drive's charset.** Every JSON answer is labeled `Content-Type: application/json; charset=UTF-8`, as Drive labels it, where it was bare `application/json`.

## v0.0.2 — 2026-10-13

1. **A comment's author carries a name and photo, and `resolved` appears only once a reply acts.**

   - Every comment's and reply's `author` carries `displayName` and a protocol-relative `photoLink` (`//lh3.googleusercontent.com/...`) from the author's user record, with `me` true only for the caller who wrote it.
   - The seeded users carry a `displayName` and a `photoLink`, so a seeded caller's comments render a full author.
   - A comment answers no `resolved` until a reply with `action` `resolve` or `reopen` acts on it, then `true` or `false`; a deleted comment read with `includeDeleted=true` carries none.

   Before, an author whose user record lacked a name or photo answered without them, and every comment answered `resolved: false`.

2. **A request with no credential answers what Drive answers on that method.**

   - Most methods answer 401 reason `required`, message "Login Required.", status `UNAUTHENTICATED`, with one `google.rpc.ErrorInfo` detail: reason `CREDENTIALS_MISSING`, service `drive.googleapis.com` and the method's own name, such as `google.apps.drive.v3.DriveFiles.Create`. Revision downloads (`revisions.get` with `alt=media`) are among them.
   - `files.list`, `files.get` and every comments and replies method answer 403 `forbidden`, status `PERMISSION_DENIED`, "Method doesn't allow unregistered callers …".
   - `files.get` with `alt=media` and `files.export` answer 403 `forbidden` "The request is missing a valid API key." `files.export` checks its required `mimeType` first.
   - An empty bearer value or another scheme counts as no credential. The check runs before the file lookup, so an unknown ID still answers the credential refusal.

   Before, most methods answered 401 `authError` "Invalid Credentials", and some carried a placeholder `example.googleapis.com` detail.

   A bearer that is present is still accepted whatever its value: the twin does not check token validity, where Drive answers a bogus token 401 `authError` "Invalid Credentials".

3. **`permissions.list` orders entries as Drive does.**

   `anyone` permissions come first, then `domain` permissions, then user permissions, where the owner's entry leads the other user grants, which follow in the order they were made. With `pageSize=1` on a file shared by link, the first page is the `anyone` permission and `nextPageToken` continues to the owner.

   Before, the owner's entry always came first.

4. **`revisions.list` refuses an unknown `pageToken`, and two comment routes check the credential first.**

   - `revisions.list` with a `pageToken` it never issued answers 400 reason `invalid`, message "Invalid Value", at location `pageToken`, as Drive does. An out-of-range `pageSize` keeps its own 400 `invalidParameter` answer.
   - `comments.update` and `replies.create` with no credential answer Drive's 403 before any other check.

   Before, an unknown `pageToken` answered the `pageSize` refusal, and those two routes answered 401 to a missing credential.

5. **A multipart upload's base64 media part is stored as its decoded bytes.** An `uploadType=multipart` upload whose media part carries `Content-Transfer-Encoding: base64` now stores, sizes and checksums the decoded bytes, so `alt=media` answers the original content; before, the twin stored the base64 text. Base64 that does not decode answers 400 `parseError`, as any malformed multipart body does.

6. **A `fields` selector naming a member the resource lacks answers 400, and `comments.list` ignores an unknown `pageToken`.**

   - A `fields` selector naming a member the method's resource does not have, at any depth (`nope`, `id,nope`, `owners(nope)`, `capabilities/nope`, `ID`), answers 400 `invalidParameter` "Invalid field selection nope" at location `fields`, as Drive does. A create refused this way still writes the file.
   - `comments.list` with a `pageToken` it never issued answers 200 with the first page, as Drive does.

   Before, such a selector answered 2xx with nothing for that member, and `comments.list` refused the token with 400 `invalidParameter`.

7. **`files.update` accepts a member of the wrong type, and an upload refused for its `fields` writes nothing.**

   - `files.update` with a member of the wrong type answers 200 as Drive does:
     - a non-string `name`, such as `{…}`, leaves the name unchanged;
     - a non-boolean `trashed`, such as `"x"`, reads as `false` and restores a trashed file;
     - any other wrong-typed member is ignored.
   - A body that is not valid JSON still answers 400 `parseError`.
   - A media or multipart upload, create or update, whose `fields` names a member the File resource lacks answers 400 and writes nothing: no file is created, and an updated file keeps its bytes, as Drive does.

   Before, the twin answered `files.update` 400 `invalidParameter` for a wrong-typed member, and wrote the refused upload.

## v0.0.1 — 2026-10-13

1. **A local Google Drive for your integration and the agents that drive it.** The first release of the Google Drive twin: 35 operations of the Drive API v3 — one user's files and folders, uploads and downloads, `files.list` with `q`, `orderBy` and page tokens, revisions, exports, permissions, and comments and replies — served on one port for `www.googleapis.com`'s `/drive/v3` and `/upload/drive/v3` routes, from a model validated against live Google Drive. Point Google's Node or Python client at it, or curl it with any bearer token (`HOWTO.md` shows how each client's uploads reach the twin, since an upload left unpointed goes to the real Google Drive); nothing reaches Google and no real Drive changes.

2. **Files and folders, created, read, trashed and deleted.** `POST /drive/v3/files` creates a file, or a folder with `mimeType` `application/vnd.google-apps.folder`, under the folders its `parents` name; `files.get`, `files.update`, `files.copy` and `files.delete` read, change, copy and delete it, and every file a shipped world seeds reads back. `PATCH` with `trashed` moves a file to the bin and back, and `DELETE /drive/v3/files/trash` empties the bin. A create naming an ID `files.generateIds` issued keeps it, an ID a file already holds answers 409 `fileIdInUse` and changes nothing, and an ID `generateIds` never issued answers 400 `fileIdNotUsable`. A missing or deleted file answers 404 `notFound` in Drive's JSON error envelope.

3. **Uploads, downloads, revisions and exports.** `uploadType=media` and `uploadType=multipart` create and update a file's bytes on `/upload/drive/v3`, as the official clients send them, and `alt=media` reads them back: a `Range` answers 206, and one past the end 416 with `Content-Range: bytes */<size>` and Drive's `requestedRangeNotSatisfiable` error. Each upload records a revision, which `revisions.list`, `get`, `update`, `delete` and `alt=media` reach; a file created from metadata alone lists its head revision, as Drive does. `files.export` converts a Docs, Sheets or Slides file, and without `mimeType` answers 400 `required` "Required parameter: mimeType". A resumable upload answers 501.

4. **`files.list` searches by `q`, sorts by `orderBy`, and `fields` selects the answer.** `q` takes Drive's grammar over `name` (`=`, `!=`, and `contains` matching the start of a word), `mimeType`, `'<folder>' in parents`, `trashed`, `starred`, `modifiedTime`, `'me' in owners` and `properties has`, joined by `and`, `or`, `not` and parentheses; `orderBy` sorts by `name`, `name_natural`, `folder`, `createdTime`, `modifiedTime` and the other documented keys, each with `desc`; a `pageSize` outside 1 to 1000 answers 400 `invalidParameter`. A `fields` selector narrows every 2xx answer to the members it names, `*` for the whole resource, and leaves an error whole; without one a route answers Drive's default members. `about.get` and every comments and replies method but delete answer 400 `required` without a selector, as Drive does.

5. **A caller's access comes from its role on the file.** A caller's role is owner, or else the highest role a permission on the file or on a folder above it grants, an `anyone` link included: a file the caller holds no role on answers 404 `notFound` and is left out of `files.list`, a reader or commenter gets 403 `insufficientFilePermissions` on an update, `permissions.list` and a share, and only the owner deletes, trashes or restores. `ownedByMe`, `capabilities`, `shared` and `permissionIds` are worked out for each caller, and `X-Rystic-Actor` names which of a world's users is calling. Only a comment's or reply's author changes or deletes it. Drive's refusals of a query parameter answer as Drive does: `useDomainAdminAccess=true` on the permissions routes 404, `sendNotificationEmail=true` on an `anyone` grant 403, role `owner` without `transferOwnership` 403, `includeItemsFromAllDrives=true` without `supportsAllDrives` 403 `supportsTeamDrivesRequired`, and `acknowledgeAbuse=true` on a metadata read 403 `invalidAbuseAcknowledgment`.

6. **Shipped Drives to boot straight into.** `--scenario google-drive_media_vault` boots Juno Park's folders of images, video and proofs, shared with a collaborator, the world `check.sh` and the quickstart use. `google-drive_shared_inbox` holds files two other people shared with Noor Haddad, `google-drive_trash_backlog` a full bin beside one live doc, `google-drive_team_workspace` a team tree Dana Okafor owns and shares with a writer and a commenter, whose roles each answer as Drive's do, and `google-drive_fresh_mydrive` an empty Drive; `SCENARIOS.md` says what each is for. In each, the default caller is the world's own user, who owns what the world says they own, and `about.get` answers that user's whole About. `POST /_rystic/seed` lands users, files, revisions with their bytes, permissions and comments directly, and `POST /_rystic/reset` rewinds the sequences and the clock, so two identical runs give byte-identical files.

7. **Measured against live Google Drive.** The full-suite round of 2026-10-12 (`20261012T222054Z`) matched 271 of 308 probes against live Google Drive, none inconclusive. `HOWTO.md` states the differences this release ships with: seeded files and copies download 0 bytes, `files.list` answers each world's `My Drive` root as a row, ownership does not move on `transferOwnership`, a request with no credential answers 401 where Drive answers 403 on a read, some `q` forms Drive takes are refused, a read-only file still answers `canModifyContent: true`, and some differences in the wire format.

---

Source: https://www.rystic.ai/docs/simulators/google-drive/changelog · Markdown: https://www.rystic.ai/docs/simulators/google-drive/changelog.md
