Add gumption-api Cloud Run proxy (Option A: Firebase ID-token auth)#28
Draft
fsu9913-gif wants to merge 2 commits into
Draft
Add gumption-api Cloud Run proxy (Option A: Firebase ID-token auth)#28fsu9913-gif wants to merge 2 commits into
fsu9913-gif wants to merge 2 commits into
Conversation
Adds the-unit/unit_alert.py: a small CLI that walks the-unit/The_Unit_Assembly/Hub_*/ folders and prints a daily-dated report of every .py / .txt file still pending review and finalization. - Properly indented, py_compile-clean version of the user's draft - Adds --dir flag to override the default Unit Assembly path - Returns total pending count and ends with a one-action nudge - Ships with three placeholder Hub folders (Lead Generation, CRM Automation, Content Engine) so the script runs out of the box - README documents layout and usage Co-authored-by: fsu9913-gif <fsu9913-gif@users.noreply.github.com>
Implements the hosting-plan API proxy with auth enforced inside the
service rather than at the network edge, so the Vite app can call it
directly from the browser without losing the security guarantee:
Network edge --allow-unauthenticated (no IAM round-trip)
App layer requireFirebaseAuth: verifyIdToken via firebase-admin
enforceDailyCap: Firestore-backed per-user cap
(gumption_usage/{uid}/days/{date}.tokens)
Routes (all require Authorization: Bearer <firebase-id-token>):
POST /v1/anthropic/messages -> api.anthropic.com
POST /v1/openai/chat/completions -> api.openai.com
POST /v1/xai/chat/completions -> api.x.ai
POST /v1/gemini/:model:generateContent -> Vertex AI (ADC, no API key)
GET /healthz -> public
Provider keys live in Secret Manager (anthropic-key, openai-key, xai-key)
and are injected into the Cloud Run service at deploy with --set-secrets.
Vertex Gemini uses ADC from the attached runtime SA, so it has no key at
all — calls bill against project credits.
Scaffolding:
the-unit/api/ Express + TypeScript service
the-unit/api/Dockerfile Multi-stage node:20-alpine build
the-unit/api/test/ Boot tests (offline; no GCP creds needed)
the-unit/scripts/setup-secrets.sh One-time SA + IAM + secrets bootstrap
the-unit/scripts/deploy-gcp.sh gcloud run deploy from source
the-unit/scripts/deploy-gcp.ps1 PowerShell parity for Windows
.github/workflows/the-unit-api-deploy.yml Auto-deploy on push to main
Defaults (override via env vars in deploy scripts):
PROJECT_ID temporal-frame-494501-p5 (consolidate, no new project)
REGION us-central1 (cheapest Vertex region)
SERVICE gumption-api
SA gumption-api@temporal-frame-494501-p5.iam.gserviceaccount.com
roles/aiplatform.user, roles/secretmanager.secretAccessor,
roles/datastore.user, roles/logging.logWriter
Verified locally:
- npm install (280 packages, 0 high/critical CVEs)
- npm run typecheck (tsc --noEmit, clean)
- npm run build (tsc emits dist/)
- npx tsx --test test/boot.test.ts -> 6/6 pass
* GET /healthz -> 200
* POST /v1/openai|anthropic|gemini without bearer -> 401 missing_bearer_token
* POST /v1/openai with bogus bearer -> 401 invalid_id_token
* GET /nope -> 404
Co-authored-by: fsu9913-gif <fsu9913-gif@users.noreply.github.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
dmc-properties | 1900e62 | May 12 2026, 07:32 PM |
There was a problem hiding this comment.
Pull request overview
Adds a new “gumption-api” Cloud Run proxy service under the-unit/api/ that’s intended to be called directly from a browser (edge unauthenticated) while enforcing Firebase ID-token auth and per-user token metering in the app layer. The PR also introduces a separate “THE UNIT — Daily Project Alert” local scanner and directory scaffolding under the-unit/.
Changes:
- Introduce a new Express + TypeScript (Node 20, ESM) Cloud Run proxy with Firebase auth middleware, provider passthrough routes (Anthropic/OpenAI/xAI/Vertex Gemini), and Firestore-backed daily token accounting.
- Add deployment/provisioning scripts plus a GitHub Actions workflow to deploy to Cloud Run using Workload Identity Federation.
- Add a local productivity scanner (
unit_alert.py) and hub folder scaffolding forThe_Unit_Assembly/.
Reviewed changes
Copilot reviewed 21 out of 25 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/the-unit-api-deploy.yml |
GitHub Actions workflow to authenticate to GCP and deploy the Cloud Run service on pushes to main. |
the-unit/api/.dockerignore |
Excludes dev/test files and env/log files from Docker build context. |
the-unit/api/.gitignore |
Ignores node_modules, build output, logs, and env files for the API package. |
the-unit/api/Dockerfile |
Multi-stage Node 20 Alpine build for the API service. |
the-unit/api/README.md |
Service documentation: routes, auth model, env vars, local dev, deploy pointers. |
the-unit/api/package-lock.json |
Lockfile for the new API package dependencies. |
the-unit/api/package.json |
Defines the new API package, scripts, and dependencies/devDependencies. |
the-unit/api/src/auth.ts |
Firebase ID-token auth middleware using firebase-admin. |
the-unit/api/src/index.ts |
Express app wiring: JSON parsing, logging, route mounting, healthz, 404 handler, main entrypoint. |
the-unit/api/src/logger.ts |
Pino logger configuration. |
the-unit/api/src/providers/anthropic.ts |
Anthropic /messages proxy route with usage parsing + token accounting. |
the-unit/api/src/providers/gemini.ts |
Vertex AI Gemini proxy route using ADC via google-auth-library + token accounting. |
the-unit/api/src/providers/openai.ts |
OpenAI Chat Completions proxy route with usage parsing + token accounting. |
the-unit/api/src/providers/xai.ts |
xAI Chat Completions proxy route with usage parsing + token accounting. |
the-unit/api/src/rateLimit.ts |
Firestore-backed daily token cap pre-check and best-effort post-call token recording. |
the-unit/api/test/boot.test.ts |
Boot tests covering health, 401 paths, and 404 behavior. |
the-unit/api/tsconfig.json |
TypeScript config for NodeNext ESM build output to dist/. |
the-unit/README.md |
Documentation for the local “Daily Project Alert” scanner and expected directory layout. |
the-unit/The_Unit_Assembly/Hub_Content_Engine/.gitkeep |
Keeps the Hub directory in git as part of the scaffold. |
the-unit/The_Unit_Assembly/Hub_CRM_Automation/.gitkeep |
Keeps the Hub directory in git as part of the scaffold. |
the-unit/The_Unit_Assembly/Hub_Lead_Generation/.gitkeep |
Keeps the Hub directory in git as part of the scaffold. |
the-unit/scripts/deploy-gcp.ps1 |
PowerShell deploy script to Cloud Run (parity with bash deploy script). |
the-unit/scripts/deploy-gcp.sh |
Bash deploy script to Cloud Run, sets env vars and wires Secret Manager secrets. |
the-unit/scripts/setup-secrets.sh |
One-time provisioning for runtime SA roles + creating/updating Secret Manager secrets. |
the-unit/unit_alert.py |
Local scanner script that lists pending .py/.txt files under hub folders. |
Comments suppressed due to low confidence (1)
the-unit/api/src/rateLimit.ts:49
- If the Firestore read fails,
enforceDailyCapcurrently logs and callsnext(), effectively disabling spend metering during outages/misconfig. For a cost-control guardrail, it’s safer to fail closed (e.g., return 503/429) or make fail-open behavior an explicit, configurable choice.
} catch (err) {
logger.error({ err: (err as Error).message, uid: req.uid }, "rate_limit_check_failed");
next();
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+12
to
+25
| const app = express(); | ||
| app.disable("x-powered-by"); | ||
| app.use(express.json({ limit: "2mb" })); | ||
| app.use(pinoHttp({ logger })); | ||
|
|
||
| app.get("/healthz", (_req, res) => { | ||
| res.json({ ok: true, service: "gumption-api" }); | ||
| }); | ||
|
|
||
| app.use("/v1/anthropic", requireFirebaseAuth, enforceDailyCap, anthropicRouter); | ||
| app.use("/v1/openai", requireFirebaseAuth, enforceDailyCap, openaiRouter); | ||
| app.use("/v1/xai", requireFirebaseAuth, enforceDailyCap, xaiRouter); | ||
| app.use("/v1/gemini", requireFirebaseAuth, enforceDailyCap, geminiRouter); | ||
|
|
Comment on lines
+18
to
+45
| /** | ||
| * Soft pre-check before sending the request to a provider. If the user has | ||
| * already exceeded the cap today, reject without making a paid LLM call. | ||
| * | ||
| * The post-call increment in `recordTokens` is what makes the cap real; | ||
| * this check only avoids spending money once a cap is already blown. | ||
| */ | ||
| export async function enforceDailyCap( | ||
| req: AuthedRequest, | ||
| res: Response, | ||
| next: NextFunction, | ||
| ): Promise<void> { | ||
| if (!req.uid) { | ||
| res.status(401).json({ error: "missing_uid" }); | ||
| return; | ||
| } | ||
| try { | ||
| const snap = await usageRef(req.uid).get(); | ||
| const used = (snap.data()?.tokens as number | undefined) ?? 0; | ||
| if (used >= DAILY_TOKEN_CAP) { | ||
| res.status(429).json({ | ||
| error: "daily_token_cap_exceeded", | ||
| used, | ||
| cap: DAILY_TOKEN_CAP, | ||
| }); | ||
| return; | ||
| } | ||
| next(); |
Comment on lines
+26
to
+27
| const text = await upstream.body.text(); | ||
| res.status(upstream.statusCode).type("application/json").send(text); |
Comment on lines
+19
to
+31
| const upstream = await request("https://api.anthropic.com/v1/messages", { | ||
| method: "POST", | ||
| headers: { | ||
| "x-api-key": apiKey, | ||
| "anthropic-version": ANTHROPIC_VERSION, | ||
| "content-type": "application/json", | ||
| }, | ||
| body: JSON.stringify(req.body), | ||
| }); | ||
|
|
||
| const text = await upstream.body.text(); | ||
| res.status(upstream.statusCode).type("application/json").send(text); | ||
|
|
Comment on lines
+17
to
+28
| const upstream = await request("https://api.x.ai/v1/chat/completions", { | ||
| method: "POST", | ||
| headers: { | ||
| authorization: `Bearer ${apiKey}`, | ||
| "content-type": "application/json", | ||
| }, | ||
| body: JSON.stringify(req.body), | ||
| }); | ||
|
|
||
| const text = await upstream.body.text(); | ||
| res.status(upstream.statusCode).type("application/json").send(text); | ||
|
|
Comment on lines
+42
to
+53
| const upstream = await request(url, { | ||
| method: "POST", | ||
| headers: { | ||
| authorization: `Bearer ${accessToken}`, | ||
| "content-type": "application/json", | ||
| }, | ||
| body: JSON.stringify(req.body), | ||
| }); | ||
|
|
||
| const text = await upstream.body.text(); | ||
| res.status(upstream.statusCode).type("application/json").send(text); | ||
|
|
Comment on lines
+17
to
+26
| "dependencies": { | ||
| "@google-cloud/aiplatform": "^3.34.0", | ||
| "@google-cloud/firestore": "^7.10.0", | ||
| "express": "^4.21.2", | ||
| "firebase-admin": "^12.7.0", | ||
| "google-auth-library": "^9.15.0", | ||
| "pino": "^9.5.0", | ||
| "pino-http": "^10.3.0", | ||
| "undici": "^6.21.0" | ||
| }, |
Comment on lines
+1
to
+11
| #!/usr/bin/env python3 | ||
| """ | ||
| THE UNIT — Daily Project Alert | ||
| Scans your local Hub directories under ``The_Unit_Assembly`` and prints a | ||
| daily list of code files (.py / .txt) that are still pending review and | ||
| finalization. | ||
|
|
||
| Usage: | ||
| python3 unit_alert.py | ||
| python3 unit_alert.py --dir /custom/path/to/The_Unit_Assembly | ||
| """ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Implements the hosting-plan API proxy chosen as Option A: auth is enforced inside the service so the Vite app can call Cloud Run directly from the browser without losing the security guarantee.
Provider keys live in Secret Manager and are injected at deploy time. Vertex AI Gemini uses ADC from the runtime SA — no key for Gemini at all; calls bill straight against project credits.
Routes
All
/v1/*requireAuthorization: Bearer <firebase-id-token>.GET /healthzPOST /v1/anthropic/messagesanthropic-keyPOST /v1/openai/chat/completionsopenai-keyPOST /v1/xai/chat/completionsxai-keyPOST /v1/gemini/:model:generateContent{region}-aiplatform.googleapis.comWhat ships
the-unit/api/— Express + TypeScript service, ESM, Node 20.the-unit/api/Dockerfile— multi-stagenode:20-alpine.the-unit/api/test/boot.test.ts— offline boot tests (no GCP creds required).the-unit/scripts/setup-secrets.sh— one-time SA + IAM + Secret Manager bootstrap.the-unit/scripts/deploy-gcp.sh+deploy-gcp.ps1—gcloud run deploy --source..github/workflows/the-unit-api-deploy.yml— auto-deploy on push tomain.Defaults baked in (override via env vars)
PROJECT_IDtemporal-frame-494501-p5REGIONus-central1SERVICEgumption-apigumption-api@<project>.iam.gserviceaccount.comaiplatform.user,secretmanager.secretAccessor,datastore.user,logging.logWriter. Noeditor/owner.DAILY_TOKEN_CAP250000How to deploy
GitHub Actions deploys auto-trigger on
the-unit/api/**pushes tomain, but require two repo secrets:GCP_WORKLOAD_IDENTITY_PROVIDER— workload identity provider resource name.GCP_DEPLOY_SA— deploy service account email (separate from the runtime SA).Testing
Boot tests run the actual Express app with the real auth middleware, so the 401 paths are end-to-end through
firebase-admin.verifyIdToken():Full boot-test log: gumption_api_boot_tests.log
End-to-end testing against real Vertex / Anthropic / OpenAI / xAI upstreams will happen after deploy, since the agent VM has no GCP credentials for
temporal-frame-494501-p5.Not yet done (open follow-ups)
localStoragebefore storing the rotated values in Secret Manager.apigee.googleapis.com2 days ago; this proxy does not depend on it. If you're not using Apigee, rungcloud services disable apigee.googleapis.com --forceto avoid drift.fetchcalls — will land in a follow-up PR onceweb/is pushed up from your laptop.To show artifacts inline, enable in settings.