# Google Drive as Server Cloud Storage — Spec

**Status:** Draft — Phase 0 helper shipped (	ools/oracle-discord/drive-oauth-setup.mjs) · **Date:** 2026-07-17 · **Owner:** Ryan
**Goal:** Use the dedicated Google account's 5TB Drive as the server's cloud
storage backend — per-user workspace files, message attachments, and per-user
memory `.md` files — with storage quotas enforced per paid tier.

---

## 1. Summary

The KAI server (`command-center-server.mjs`) gains a **Drive storage module**
that stores each user's files in their own folder inside the 5TB Drive, tracks
how many bytes each user has used, and refuses uploads that would exceed the
user's tier limit. Files are addressed by the **real user id** from the identity
isolation fix (v9.10 / 2026-07-17), so storage is correctly attributed per user.

This is additive. Nothing about the existing engine, Discord fleet, or dashboard
changes except new `/api/storage/*` endpoints and a new server module.

---

## 2. Account & auth model (the decision that drives everything)

The 5TB lives on a **dedicated account you created for this**. That's the clean
case: the server can act fully *as* that account with no risk to a personal
identity.

**Chosen path: OAuth2 with a stored refresh token, scope `drive.file`.**

Ranked, with why:

1. **OAuth2 refresh token, `drive.file` scope — RECOMMENDED (high confidence).**
   The server authenticates as the dedicated account and can create/read/manage
   *only the files it creates* (`drive.file` = least privilege; it cannot see
   anything else in that Drive). Files count against the account's 5TB. One-time
   consent → long-lived refresh token stored in `.env`.
2. **OAuth2 with full `drive` scope — works, not recommended.** Broader access
   than needed. Only use if we later need to read files the server did not create.
3. **Service account + Shared Drive — only if this becomes a Workspace account.**
   Personal Gmail accounts cannot host Shared Drives, so this is off the table
   for the current dedicated Gmail. If you later move to Google Workspace, this
   becomes the cleanest option (no refresh-token maintenance).
4. **Plain service account (no shared drive) — RULED OUT.** A service account has
   its own ~15GB quota and separate billing; files it creates do **not** draw
   from the 5TB. This is the common trap.
5. **`GOOGLE_API_KEY` / Vertex ADC (already in `.env`) — RULED OUT for storage.**
   Neither carries a Drive scope; they cannot access Drive user storage.

---

## 3. One-time setup (YOU do this — I cannot log into your Google account)

This is the only part I can't do for you: creating the credential and doing the
consent both require signing into the dedicated Google account. Steps:

1. **Enable the API.** Google Cloud Console → the existing project (the one in
   `ANTIGRAV_PROJECT`) → *APIs & Services → Library* → search **Google Drive
   API** → **Enable**. (Reusing your Antigravity project is fine.)
2. **OAuth consent screen.** *APIs & Services → OAuth consent screen* → set it to
   **External**, add the dedicated account's email as a **Test user** (so it works
   without Google verification), add scope `.../auth/drive.file`.
3. **Create credentials.** *Credentials → Create credentials → OAuth client ID →
   Application type: Desktop app.* Download the JSON — it contains
   `client_id` and `client_secret`.
4. **Get a refresh token (one time).** I'll ship a tiny helper script
   `tools/oracle-discord/drive-oauth-setup.mjs`. You run it once:
   - It prints a Google URL. Open it, sign in **as the dedicated account**,
     approve.
   - Google redirects with a `code`; the script exchanges it for a
     **refresh token** and prints it.
   - `access_type=offline` + `prompt=consent` are set so a refresh token is
     actually returned.
5. **Paste 3 values into `.env`** (see §4). Done — the server can now use the 5TB.

> Security: the refresh token is a long-lived secret. It lives only in
> `tools/oracle-discord/.env` (same as every other secret) — never printed,
> committed, or echoed.

---

## 4. New `.env` keys

```
GDRIVE_CLIENT_ID=<from the OAuth client JSON>
GDRIVE_CLIENT_SECRET=<from the OAuth client JSON>
GDRIVE_REFRESH_TOKEN=<from the one-time setup script>
GDRIVE_ROOT_FOLDER_NAME=KAI-Server          # optional; default "KAI-Server"
GDRIVE_ENABLED=1                            # kill-switch: 0 = disable cleanly
```

No new secrets are needed beyond these three real ones.

---

## 5. Server storage module — `tools/oracle-discord/storage/drive.mjs`

Uses the `googleapis` npm package (add to `package.json`). Singleton Drive client
built from the refresh token; token auto-refreshes in memory.

Drive layout:

```
KAI-Server/                (root app folder, id cached)
  users/
    <userId>/              (one folder per real user id)
      workspace/           (Builder+ workspace files)
      attachments/         (message/DM attachments)
      memory/              (per-user .md memory files — task #20)
```

Functions (all async, all keyed by the caller's real `userId`):

- `ensureUserFolder(userId, sub)` → folderId (creates lazily, caches id).
- `uploadFile(userId, sub, name, buffer, mime)` → `{fileId, size, webViewLink}`.
- `downloadFile(fileId, userId)` → stream (verifies the file is under the user's
  folder before serving — no cross-user reads).
- `listFiles(userId, sub)` → `[{id,name,size,modifiedTime}]`.
- `deleteFile(fileId, userId)` → moves to trash (never hard-delete).
- `userUsageBytes(userId)` → summed size of the user's subtree (cached ~60s).
- `accountQuota()` → `about.get({fields:'storageQuota'})` → `{limit,usage}` for
  the whole 5TB (ops dashboard).

Design notes:
- **`drive.file` scope** means the module only ever sees its own files — safe.
- **Ownership check on every read/delete**: resolve the file's parents and confirm
  it's inside `users/<callerId>/`. This is the storage-side equivalent of the
  identity isolation fix — a user can never fetch another user's file id.

---

## 6. Per-tier quota model

Drive has no native per-subfolder quota, so the **server enforces it**: before an
upload, `userUsageBytes(userId) + incoming.size` must be ≤ the user's tier limit.
Usage is also cached in the user record for fast reads.

Proposed starting limits (adjust — see §10; total must fit inside 5TB):

| Tier        | Storage/user | Notes                                  |
|-------------|--------------|----------------------------------------|
| Free        | 1 GB         | attachments (no full workspace folder) |
| Explorer    | 5 GB         | paid tools + browser                   |
| Creator     | 25 GB        | workspace files                        |
| Builder     | 75 GB        | + Antigravity coding workspace         |
| Team        | 150 GB       | Roundtable + Full Fleet                |
| Enterprise  | 1 TB         | baseline; config can raise             |

Capacity math: 5TB = ~5,000 GB. Mixed tiers leave comfortable headroom for a
few hundred paid users; the ops dashboard shows total 5TB usage via
`accountQuota()` so you see the ceiling coming.

Enforcement returns HTTP 413 `{ok:false,error:'quota_exceeded',limit,used}` so
the UI can show "You're out of space — upgrade your tier."

Tier limits live in one server constant (`TIER_STORAGE_BYTES`) next to the
existing plan/credits config, so they're one edit to tune.

---

## 7. New API endpoints (all behind `currentContentUser` → real user only)

```
POST   /api/storage/upload      (multipart or base64) → {fileId,size,link}
GET    /api/storage/list?sub=   → user's files in a subfolder
GET    /api/storage/file?id=    → download (ownership-checked)
POST   /api/storage/delete      {fileId} → trash
GET    /api/storage/usage       → {used, limit, tier, pct}
GET    /api/storage/account     (owner/admin only) → whole-5TB usage
```

These reuse the same auth wall and per-user identity as the DM/receipt work.

---

## 8. Consumers to wire (after the module lands)

1. **Message / DM attachments** — the "Photo" button and DM images upload to
   `attachments/`, store the returned link in the post/message record.
2. **Per-user memory `.md`** (task #20) — write/read the user's memory file in
   `memory/`, with the local device copy as the fast cache and Drive as backup.
3. **Builder-tier workspace** — the Antigravity workspace files for Builder users
   live in `workspace/` (ties into the "Builder tier → Antigravity session"
   request).

---

## 9. Rollout phases

- **Phase 0 (you):** §3 setup → 3 `.env` values. ~15 min.
- **Phase 1:** `drive.mjs` module + `npm i googleapis` + a self-test that uploads
  and deletes a 1KB probe file. Verifiable in isolation, touches nothing else.
- **Phase 2:** `/api/storage/*` endpoints + `TIER_STORAGE_BYTES` + quota gate.
- **Phase 3:** wire attachments.
- **Phase 4:** wire per-user memory + Builder workspace.
- Each phase is independently shippable and reversible via `GDRIVE_ENABLED=0`.

---

## 10. Risks / gotchas (honest list)

- **Refresh-token expiry.** For an app in "Testing" on the consent screen, refresh
  tokens can expire after 7 days. **Fix:** move the consent screen to
  "In production" (no Google verification needed for `drive.file` with a single
  known user) — then the refresh token is long-lived. I'll flag this in the setup
  steps so you don't get surprised in a week.
- **Quota accounting drift.** Summing file sizes can drift if files are added out
  of band. **Mitigation:** periodic reconcile against Drive's actual per-folder
  listing; usage cache TTL ~60s.
- **Rate limits.** Drive API has per-project quotas (generous, but real). Batch
  and cache; don't poll usage on every request (cache it).
- **Large uploads.** Use resumable uploads for files > ~5MB (the module handles
  this).
- **Single point of storage.** Everything rides one Drive account; if that account
  is locked, storage is unavailable. Acceptable for now; note it for scale.
- **Not a CDN.** Serving files through the server proxies bytes. Fine for
  attachments; if you later want fast public media, add signed Drive links or a
  real object store (R2/S3) — but 5TB Drive is a great start.

---

## 11. Open decisions for you

1. **Tier storage sizes** — accept the §6 table or give me your numbers.
2. **`drive.file` vs full `drive` scope** — recommend `drive.file` (least
   privilege). Confirm.
3. **Attachment size cap per upload** (e.g., 25MB Free → 250MB Builder?).
4. **When to move the OAuth consent screen to "production"** (do it during setup
   to avoid the 7-day token expiry — recommended).

Once you've done Phase 0 and answered §11, I'll build Phase 1 and give you the
probe-file test to confirm the 5TB is live before wiring anything else.
