Skip to content

gdocs: documents.get answers a reader as real does, serves the document as its tab, reads the comments modes real reads and trims to a fields mask - #1055

Merged
khj809 merged 35 commits into
mainfrom
docs-get-params-1042
Oct 29, 2026
Merged

khj809 merged 35 commits into
mainfrom
docs-get-params-1042

Conversation

@khj809

@khj809 khj809 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Closes #572. Closes #574. Part of #1042, on top of the document body and defaults #1052 serves through documents.get.

What changed

A reader is answered as real answers one. A caller who is not a document's owner is a reader here (routers.google._drive_file_capabilities), the admin token of every file it did not create among them (routers.google._drive_owned_by). A reader's documents.get carries no revisionId, its suggestionsViewMode is PREVIEW_WITHOUT_SUGGESTIONS unless it asks for another, and asking for SUGGESTIONS_INLINE or PREVIEW_SUGGESTIONS_ACCEPTED is the 403 You do not have permission to access the document suggestions. The owner's default stays SUGGESTIONS_INLINE.

suggestionsViewMode is echoed. A mode asked for comes back by its name, a number included, where every answer said SUGGESTIONS_INLINE.

The tabbed answer is served. includeTabsContent=true, or a fields mask naming tabs (tabs, *.tabProperties), answers title, revisionId, suggestionsViewMode, documentId, tabs, commentsViewMode in that order, with one tab, {"tabId": "t.0", "title": "Tab 1", "index": 0}, whose documentTab holds the body, documentStyle and namedStyles the flat answer serves. fields=*, *.* and tabs() still answer the flat document.

commentsViewMode is read. Any mode but COMMENTS_VIEW_MODE_UNSPECIFIED without the tabs, COMMENTS_VIEW_MODE_INCLUDED without an explicit SUGGESTIONS_INLINE, and either mode that asks for comments beside an explicit preview are each real's 400. COMMENTS_VIEW_MODE_DEFAULT_FOR_CURRENT_ACCESS and COMMENTS_VIEW_MODE_INCLUDED answer COMMENTS_VIEW_MODE_INCLUDED to the owner and COMMENTS_VIEW_MODE_OMITTED to a reader. No Doc here holds a comment or a suggestion, so neither a comments mode nor a preview changes the content.

A fields mask trims the answer. documents.get reads the first fields with the helpers the Sheets reads use, against the Docs types the google_drive baseline records, so a field real declares and Backlot does not build answers 200 without it. Members keep real's order whatever order the mask names them in. A repeated suggestionsViewMode, includeTabsContent or commentsViewMode is read at its last value. A path naming nothing and an unpaired bracket are the 400s whose detail names the whole mask. A mask naming a comment-specific field (comments, suggestions, a tab's commentAnchors, a child tab's at any depth included) when no comments mode asked for comments, and one naming a field a tab carries as well (body, documentStyle, namedStyles, lists and eight more) beside the tabs, are real's two other 400s. _gmask_select now returns what each path names rather than whether it names comments, so Sheets' comment-field refusal reads the same tree; its answers are unchanged.

Order. The credential, the typed values, the mask, the lookup, a reader's suggestions 403, the commentsViewMode 400s, the comment-field 400 and the tab-field 400, each ahead of the next when both hold.

Where it lives. documents.get moves to backlot/routers/google_docs.py, registered on routers.google.router the way routers.google_sheets is, for the Docs writes in #1042 to join. The fields mask helpers (_gmask_select, _gmask_apply and what they call) move from routers.google_sheets to routers.google, and both editor modules import them from there.

Fidelity baseline. The Docs walk reads as the documents' owner, flat and with includeTabsContent=true, so the baseline records docs.Tab, docs.DocumentTab and docs.TabProperties as served and the four query parameters as declared. The new gaps are what a one-tab Doc with no comments never carries: child tabs, a tab's icon, nesting and parent, and the documentTab maps (headers, lists, commentAnchors, …) already gaps on docs.Document. The commentsViewMode family note moves to the Slides parameter, the one of the three still unread.

Not modelled: what COMMENTS_VIEW_MODE_INCLUDED and the preview modes add to a Doc holding comments or suggestions, and a commenter or a writer, which no grant here makes: real serves a writer the revisionId, SUGGESTIONS_INLINE default and COMMENTS_VIEW_MODE_INCLUDED it serves the owner, so edit access rather than ownership decides them.

Why this is what the real API does

Measured on 2026-10-26 against docs.googleapis.com with a Google Workspace member's OAuth token on a one-tab Doc it owns and a two-tab Doc it owns, and a second member holding reader access to the two-tab Doc, a fresh quotaUser on each request: 128 requests, 22 of them as the reader, on top of the 2026-10-09 measurements #572 and #574 record. On 2026-10-28 a second Workspace member replayed 89 requests as an owner, a reader, a writer and on a missing id, adding the child-tab and repeat rows below.

Measurement Result
A reader's read under each suggestionsViewMode, with and without the tabs and each comments mode no revisionId; PREVIEW_WITHOUT_SUGGESTIONS by default; SUGGESTIONS_INLINE and PREVIEW_SUGGESTIONS_ACCEPTED the 403, ahead of a comments 400 and a tab-field 400; DEFAULT_FOR_CURRENT_ACCESS comments answered OMITTED, and fields=comments taken beside it
The tabbed answer under includeTabsContent, fields=tabs, tabs,title, tabs.tabProperties, *.tabProperties, *.documentTab, tabs.childTabs, *, *.*, tabs() member order above; a mask naming tabs selects it, * alone does not
A mask naming each of the twelve Document fields DocumentTab also declares, beside includeTabsContent=true or tabs each the tab-field 400, *.content included; tabs.documentTab.body is not one
Comment-specific fields: comments, suggestions, suggestions.*, *.commentId, tabs.documentTab.commentAnchors, tabs.*.commentAnchors, *.*.commentAnchors, tabs.documentTab.*, *.*.* the first seven the comment-field 400 unless a mode asked for comments; the last two answered
A child tab's anchors: tabs.childTabs.documentTab.commentAnchors, *.childTabs.documentTab.commentAnchors, tabs.childTabs.*.commentAnchors, tabs.*.*.commentAnchors, tabs.*.*.*.commentAnchors, tabs.childTabs.childTabs.documentTab.commentAnchors each the comment-field 400 on a one-tab Doc; the first 200 {"tabs": [{}]} with includeTabsContent=true&commentsViewMode=1
Each typed parameter repeated: suggestionsViewMode=1&suggestionsViewMode=3, includeTabsContent=true&includeTabsContent=false, commentsViewMode=0&commentsViewMode=2 the last value read: PREVIEW_WITHOUT_SUGGESTIONS, the flat document, the tabs 400
Each pair of refusals that can both hold, and each against an id that does not exist the order above; the mask's 400 before the lookup, every other refusal after it
The comments modes against each suggestions mode, as owner the three 400s and the INCLUDED/OMITTED answers above
The 403, a comments-mode 400, the comment-field 400 and the tab-field 400 at $.xgafv=1 reason: forbidden / badRequest under global

Verification

  • Tests — pytest on a uv sync --all-extras install with PYTHONWARNDEFAULTENCODING=1: tests/test_google.py, test_fidelity.py, test_importer_byo.py, test_schema.py, test_openapi.py, test_mcp.py and test_docs.py, 2078 passed; the whole suite is CI's run on this head. The new Docs tests fail on main without this change.

  • Optional surfaces that ran rather than skipped — mcp, official-sdk, llamaindex apart from the HubSpot reader, mirage, fsspec.

  • Fidelity — backlot diff --source google_drive: nothing new.

  • Lint — ruff check . && ruff format --check .: clean; scripts/prose_check.py: no findings.

  • A test fails without this change

  • A new endpoint serving corpus content is ACL-scoped, proved for both an admin and a scoped token

  • Comments state what was measured, not the history of the fix

🤖 Generated with Claude Code

khj809 added 22 commits October 26, 2026 14:50
…be written, and every Drive reader reads the merged source
…adsheet's cells, typed and formatted as real stores them
…oot, sheets.copyTo copies a sheet, and DataFilters address writes and clears
…fusal order, and the batch writes' query options match real
… writes are listed with the Google endpoints
…its body with UTF-16 indices and real's styles
…ries its scheme, and the characters insertText strips are refused in paragraphs and left out of a prose reading
…ent as its tab, reads the comments modes real reads and trims to a fields mask
… tabs, and the baseline records the tab types
@khj809 khj809 changed the title google: documents.get answers a reader as real does, serves the document as its tab, reads the comments modes real reads and trims to a fields mask gdocs: documents.get answers a reader as real does, serves the document as its tab, reads the comments modes real reads and trims to a fields mask Oct 26, 2026
@khj809 khj809 self-assigned this Oct 26, 2026
@khj809 khj809 added the fidelity The vendor API defines the right answer; closing this needs a measurement against it label Oct 26, 2026
# Conflicts:
#	backlot/errors/google.py
#	backlot/fidelity/baseline/google_drive.json
#	backlot/fidelity/google_walks.py
#	backlot/gsheets.py
#	backlot/importer/byo.py
#	backlot/routers/google.py
#	backlot/routers/google_sheets.py
#	backlot/store.py
#	docs/write-overlay.md
#	tests/test_google.py
…et-params-1042

# Conflicts:
#	backlot/routers/google_sheets.py
#	tests/test_google.py
Base automatically changed from docs-structure-1042 to main October 28, 2026 05:58
# Conflicts:
#	backlot/fidelity/baseline/google_drive.json
#	backlot/fidelity/google_walks.py
#	backlot/gdocs.py
#	backlot/importer/byo.py
#	backlot/routers/google.py
#	backlot/schemas/google_drive.schema.json
#	docs/corpus.md
#	tests/test_google.py
#	tests/test_importer_byo.py
#	tests/test_schema.py
@khj809
khj809 marked this pull request as ready for review October 28, 2026 08:04
@khj809
khj809 requested a review from nuriyoo October 28, 2026 08:04

@nuriyoo nuriyoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting one change. The reader's answer, the tabbed answer, the comments modes, the refusal order and the mask each answer as real does on every row I replayed; one family of masks real refuses is answered 200 here. The notes are prose, each with a suggestion.

Measured 2026-10-28 against docs.googleapis.com with a Workspace member's token, a fresh quotaUser on each request: on a one-tab Doc it owns, on two one-tab Docs a consumer account made and shared with it as a reader and as a writer, and on an id that does not exist. 89 requests: 59 as the owner, 20 as the reader, 4 as the writer, 6 on the missing id. The 85 owner, reader and missing-id rows replayed against 10376a48 as the sample document's owner, cfo@acme.com and the admin token: 79 agree on status, message, member order, the two modes, revisionId and the tab shape, and the 6 that differ are one family.

request, as the owner                                        real                           10376a48
fields=tabs.childTabs.documentTab.commentAnchors             400 comment-specific fields    200 {"tabs": [{}]}
fields=*.childTabs.documentTab.commentAnchors                400 comment-specific fields    200 {"tabs": [{}]}
fields=tabs.childTabs.*.commentAnchors                       400 comment-specific fields    200 {"tabs": [{}]}
fields=tabs.*.*.commentAnchors                               400 comment-specific fields    200 {"tabs": [{}]}
fields=tabs.*.*.*.commentAnchors                             400 comment-specific fields    200 {"tabs": [{}]}
fields=tabs.childTabs.childTabs.documentTab.commentAnchors   400 comment-specific fields    200 {"tabs": [{}]}
the first, &includeTabsContent=true&commentsViewMode=1       200 {"tabs": [{}]}             200 {"tabs": [{}]}

_docs_comment_fields reads commentAnchors under tabs.documentTab alone, and real reads it under a child tab's documentTab at any depth. The inline suggestion walks childTabs, with three of the refusals and the 200 as rows: the rows fail on this head and pass with the walk, and with the walk the Docs and Sheets tests pass and ruff and scripts/prose_check.py are clean.

Rows that agree, past the body's table, so they need not be shot again: a repeated suggestionsViewMode, includeTabsContent or commentsViewMode is read at its last value (suggestionsViewMode=1&suggestionsViewMode=3 answers PREVIEW_WITHOUT_SUGGESTIONS; commentsViewMode=2&commentsViewMode=0 is 200 with the tabs and the tabs 400 without them); fields=tabs&commentsViewMode=COMMENTS_VIEW_MODE_OMITTED and fields=tabs&commentsViewMode=3&suggestionsViewMode=1 are 200 with no includeTabsContent, where fields=*.*&commentsViewMode=2 and fields=tabs()&commentsViewMode=2 are the tabs 400; the reader's includeTabsContent=true&commentsViewMode=3&suggestionsViewMode=1 is the 403 and its includeTabsContent=true&commentsViewMode=3 the inline-only 400; fields=revisionId to the reader is {}; tabs.documentTab.commentAnchors.commentId is the mask's own 400 ahead of the comment-field one; suggestedDocumentStyleChanges and body.content.paragraph.elements.textRun.suggestedInsertionIds are answered.

A writer, which no grant here makes: real serves one revisionId and SUGGESTIONS_INLINE by default, and COMMENTS_VIEW_MODE_INCLUDED under DEFAULT_FOR_CURRENT_ACCESS, the owner's answers, so edit access rather than ownership decides them. Nothing to change in this PR; the "Not modelled" line could name the writer beside the commenter.

Mutants. 31 over the Docs route and the mask helpers on this head, 27 caught, four survivors. Three are the repeat direction: [-1] read as [0] on each of the three typed parameters leaves the suite green, and real reads the last; the inline rows hold them, measured. The fourth is main's: spreadsheets:getByDataFilter reading comments off the selection tree rather than off what the paths name is told apart by fields=*.* on that route alone, which no row sends there, on main as here. With the suggestions applied the three are caught, and so is the walk with childTabs taken out of it.

Suites. On this head with every extra installed, tests/test_google.py, test_openapi.py, test_docs.py, test_fidelity.py and test_schema.py: 1779 passed, 0 skipped, and the same with every suggestion applied. backlot diff --source google_drive: nothing new. The OpenAPI document declares the four query parameters once each, beside $.xgafv, access_token and oauth_token. Of the PR's 20 Docs tests run on main's code, 4 fail: test_docs_get_serves_members_in_reals_order, test_documents_get_refuses_the_view_real_refuses, test_documents_get_trims_to_a_fields_mask_as_real_does and the fields=nope row of the bad-query test.

Notes inline, none blocking: the module's "every answer below … on 2026-10-09 and 2026-10-26" against the 2026-10-14 and 2026-10-16 dates its own comments carry; "the admin token one too" against a file the admin token creates; the registration sentence both editor modules carry word for word; one 110-column docstring line.

Comment thread backlot/routers/google_docs.py Resolved
Comment thread tests/test_google.py Resolved
Comment thread tests/test_google.py Resolved
Comment thread tests/test_google.py Resolved
Comment thread tests/test_google.py Resolved
Comment thread backlot/routers/google_docs.py Resolved
Comment thread backlot/routers/google_docs.py Resolved
Comment thread backlot/routers/google_docs.py Resolved
Comment thread backlot/routers/google.py Resolved
…ent-field 400, and a repeated documents.get parameter is read at its last value under test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@khj809
khj809 requested a review from nuriyoo October 28, 2026 10:31

@nuriyoo nuriyoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every first-round point is in at 93ad6928. The branch differs from the suggestions in two places, and both are right: the module docstring's range ends 2026-10-28, the UTC date the child-tab rows were measured, and test_docs_get_serves_members_in_reals_order names "the admin token on a corpus file", which is the Doc its document_id fixture picks.

Checked on this head, each mutation applied alone:

mutation                                      the test that fails
suggestionsViewMode read at [0], not [-1]     test_docs_get_serves_members_in_reals_order
includeTabsContent read at [0], not [-1]      test_docs_get_serves_members_in_reals_order
commentsViewMode read at [0], not [-1]        test_documents_get_refuses_the_view_real_refuses
child tabs not walked                         test_documents_get_refuses_the_view_real_refuses
no tab's commentAnchors read                  test_documents_get_refuses_the_view_real_refuses

The seven test files the body names pass, 2077 passed with the MCP test that needs Docker skipped here. Merged into main at 96c2cebd the branch has no conflict, scripts/gen_docs.py --check is clean, and those files with tests/test_telemetry.py give 2280 passed.

Not blocking: four lines end short where the next line's first word fits on them. One is backlot/routers/google_docs.py:117, ending "and a", the last line of my suggestion on that docstring; the others are backlot/routers/google.py lines 2, 4450 and 4959.

@khj809
khj809 merged commit 820ee96 into main Oct 29, 2026
8 checks passed
@khj809
khj809 deleted the docs-get-params-1042 branch October 29, 2026 02:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fidelity The vendor API defines the right answer; closing this needs a measurement against it

Projects

None yet

2 participants