Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/src/types/freebuff-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type FreebuffCountryBlockReason =
| 'anonymous_network'
| 'missing_client_ip'
| 'unresolved_client_ip'
| 'ip_privacy_lookup_failed'

export type FreebuffIpPrivacySignal =
| 'anonymous'
Expand Down
10 changes: 10 additions & 0 deletions docs/freebuff-waiting-room.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ CREATE TABLE free_session (
status free_session_status NOT NULL,
active_instance_id text NOT NULL,
model text NOT NULL,
country_code text,
cf_country text,
geoip_country text,
country_block_reason text,
ip_privacy_signals text[],
client_ip_hash text,
country_checked_at timestamptz,
queued_at timestamptz NOT NULL DEFAULT now(),
admitted_at timestamptz,
expires_at timestamptz,
Expand All @@ -87,6 +94,7 @@ Migrations: `packages/internal/src/db/migrations/0043_vengeful_boomer.sql` (init
- **PK on `user_id`** is the structural enforcement of "one session per account". No app-logic race can produce two rows for one user.
- **`active_instance_id`** rotates on every `POST /session` call. This is how we enforce one-CLI-at-a-time (see [Single-instance enforcement](#single-instance-enforcement)).
- **`model` column.** Populated by the POST handler; determines which queue the row belongs to while queued and is fixed for the life of an active session. Switching models while an active session is live is rejected (`model_locked`, 409).
- **Country/privacy columns.** Populated from the POST `/session` country gate so active-session audits can see the resolved country, Cloudflare country header, GeoIP fallback country, IPinfo privacy signals, and a keyed hash of the client IP. Raw IPs are not stored.
- **All timestamps server-supplied.** The client never sends `queued_at`, `admitted_at`, or `expires_at` — they are either `DEFAULT now()` or computed server-side during admission.
- **FK CASCADE on user delete** keeps the table clean without a background job.

Expand Down Expand Up @@ -170,6 +178,8 @@ All endpoints authenticate via the standard `Authorization: Bearer <api-key>` or
- Existing active+unexpired row, **different model** → reject with `model_locked` (HTTP 409); `active_instance_id` is **not** rotated so the other CLI stays valid. Client must DELETE the session before switching.
- Existing active+expired row → reset to queued with fresh `queued_at` and the requested `model` (re-queue at back).

Before any of those state transitions, the handler requires a resolved allowlisted country and a successful IPinfo privacy check. IPinfo `anonymous`, `vpn`, `proxy`, `tor`, `relay`, `res_proxy`, `hosting`, and `service` signals are blocked; privacy lookup failures fail closed.

Response shapes:

```jsonc
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ALTER TABLE "free_session" ADD COLUMN "country_code" text;--> statement-breakpoint
ALTER TABLE "free_session" ADD COLUMN "cf_country" text;--> statement-breakpoint
ALTER TABLE "free_session" ADD COLUMN "geoip_country" text;--> statement-breakpoint
ALTER TABLE "free_session" ADD COLUMN "country_block_reason" text;--> statement-breakpoint
ALTER TABLE "free_session" ADD COLUMN "ip_privacy_signals" text[];--> statement-breakpoint
ALTER TABLE "free_session" ADD COLUMN "client_ip_hash" text;--> statement-breakpoint
ALTER TABLE "free_session" ADD COLUMN "country_checked_at" timestamp with time zone;
Loading
Loading