# Continuity ledger (per-branch)

## Human intent (must not be overwritten)

- Protect the public App Run API (`POST /api/apps/[appId]/run`) from abuse with a team-scoped rate limit that can vary by team plan.

## Goal (incl. success criteria)

- Add rate limiting to `apps/studio.giselles.ai/app/api/apps/[appId]/run/route.ts` keyed by owning team, using per-plan limits, returning 429 with RateLimit headers on exceed.

## Constraints/Assumptions

- Migration SQL will be generated/applied via Drizzle separately; only `db/schema.ts` is updated here.
- Rate limit is per-team (owner of the App), not per IP.
- Keep implementation minimal and consistent with existing Drizzle patterns.

## Key decisions

- Use a Postgres-backed fixed 60-second window counter with atomic upsert (`INSERT .. ON CONFLICT DO UPDATE count=count+1`).
- Resolve `teamDbId` + `teams.plan` via `apps` → `teams` join using Drizzle in the route handler.
- Return `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, and `Retry-After` on 429.

## State

- `/api/apps/[appId]/run` is authenticated via API secret (`verifyApiSecretForApp`) and creates/starts a task via `giselle`.

## Done

- Added `api_rate_limit_counters` table definition to `apps/studio.giselles.ai/db/schema.ts`.
- Added rate limit helper at `apps/studio.giselles.ai/app/api/_lib/rate-limit.ts`.
- Wired rate limiting into `apps/studio.giselles.ai/app/api/apps/[appId]/run/route.ts` and returns 429 with headers when exceeded.
- Added unit tests for plan mapping + window/header behavior.
- Fixed `getWindowStart` to avoid hardcoding `60_000` so fixed-window math remains correct if `WINDOW_SECONDS` changes (prevents incorrect boundary alignment).

## Now

- Tests could not be executed in this environment because `node_modules` is missing (`vitest` not found). Code compiles and lints cleanly.

## Next

- Generate/apply the Drizzle migration for `api_rate_limit_counters`.
- Run `pnpm -F studio.giselles.ai test --filter rate-limit` after dependencies are installed.

## Open questions (UNCONFIRMED if needed)

- None.

## Working set (files/ids/commands)

- `apps/studio.giselles.ai/db/schema.ts`
- `apps/studio.giselles.ai/app/api/_lib/rate-limit.ts`
- `apps/studio.giselles.ai/app/api/_lib/rate-limit.test.ts`
- `apps/studio.giselles.ai/app/api/apps/[appId]/run/route.ts`
- `pnpm -F studio.giselles.ai test --filter rate-limit`

