The Google equivalent of annex-tier2.md. Everything in that annex still applies — preference order, client-owned credentials, per-project credentials, checking granted scopes after consent, backup and restore, monitoring. Only the concrete triple changes: where it runs, how access is scoped, and where the secret lives.
Written as its own file rather than as a set of parentheses in the Microsoft annex, because a Workspace client should not have to read someone else's stack to find out how their own would work.
Where the code runs
Preference order, same logic as tier 2:
- Inside the client's own project. Two shapes, and the smaller one is usually right:
- Apps Script with a time-driven trigger. Lives in the client's Workspace, no cloud project to create, no billing account to argue about, and it can read Drive and Sheets natively. For a nightly aggregate over a folder of exports this is often the whole answer.
- Cloud Run job or Cloud Function on Cloud Scheduler, in the client's own Google Cloud project, when the work outgrows Apps Script quotas or needs a real runtime.
- My EU-region infrastructure with client-owned credentials — same conditions as tier 2: Art. 30 register, DPA in place first, and nothing kept between runs (extract deleted once the aggregate is built, all copies gone within seven days of the engagement ending).
- My VPS — only when neither fits. See
annex-selfhosted.md.
Region: europe-west*. I record the configured region string from the console, not the default. Apps Script does not expose a region, which is worth stating plainly to a client who asks — the data stays inside their Workspace, but the "which region" question does not have the same crisp answer it has on Cloud Run.
Access — one folder, never the domain
The principle is identical to Sites.Selected: grant access to one container, not to a directory. Google makes the narrow path the slightly harder one, which is exactly why the broad one is so common.
- A service account the client owns, created in their project, with the target Drive folder shared to the service account's address exactly as it would be shared with a colleague. The share is the permission — there is no separate grant to audit.
- Never domain-wide delegation. It lets the service account impersonate any user in the domain, and it is the most over-granted thing in Workspace. If a vendor asks for it to read one folder, that is worth refusing — including if the vendor is me. It is justified only when the job genuinely must act as many different users, which a reporting pipeline does not.
- Prefer a shared drive over a personal My Drive folder. Access to a shared drive outlives the person who created it; a My Drive folder disappears with the account, which is a bus-factor problem hiding as a permissions choice.
- Scopes stay read-only:
drive.readonlyorspreadsheets.readonly, never the read-write pair. The pipeline reads. - Revocation is unsharing the folder or disabling the key, and the client can do both without asking me.
- Every grant goes in
templates/access-register.mdwith the date granted and, later, the date revoked and the date I confirmed I was locked out.
Checking what was actually granted: the OAuth consent screen and the granted scopes are not always the same, so after setup I list the service account's keys and check the folder's sharing panel, and I recommend the client does too.
Secrets
- Client on Google → Secret Manager in their project,
europe-west*, with access granted to the job's own service-account identity rather than to a person. Where the job runs on Cloud Run, workload identity means there is no key file at all — which is the better answer, because the safest key is the one that does not exist. - Apps Script → script properties for configuration, and nothing that is genuinely a secret if it can be avoided; where a secret is unavoidable the script reads it from Secret Manager rather than holding it.
- Service-account key files are a last resort. If one exists it is created with an expiry, stored in the vault rather than on disk, never committed, and rotated at handover and whenever anyone with access leaves.
Everything else from annex-tier2.md holds unchanged: injected at runtime, never committed, never logged, never pasted into chat or a ticket, and rotatable without a code change.
What differs when the client asks
Two honest answers a Workspace client is more likely to need than a Microsoft one:
- "Does it work with our Google Sheets directly?" Yes, and that is usually better than an exported file, because there is no manual export step to forget. It also means the sheet's own sharing settings become part of the access story, so they go in the data-flow diagram.
- "Can it run without a Google Cloud project?" Usually yes, via Apps Script — which is the lower-friction option for a small team, at the cost of the region answer above and tighter execution limits.