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
73 changes: 73 additions & 0 deletions .github/workflows/production-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Production smoke

on:
schedule:
- cron: "*/15 * * * *"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: interviewthread-production-smoke
cancel-in-progress: true

jobs:
smoke:
runs-on: ubuntu-latest
timeout-minutes: 5
env:
SITE_ORIGIN: https://interviewthreadai.com
steps:
- name: Check production homepage and health endpoint
shell: bash
run: |
set -euo pipefail

response_dir="$(mktemp -d)"
trap 'rm -rf "$response_dir"' EXIT

request() {
local name="$1"
local url="$2"
curl \
--silent \
--show-error \
--location \
--connect-timeout 10 \
--max-time 30 \
--retry 2 \
--retry-delay 2 \
--retry-all-errors \
--header "User-Agent: InterviewThread production smoke/1.0" \
--dump-header "$response_dir/$name.headers" \
--output "$response_dir/$name.body" \
--write-out "%{http_code}" \
"$url"
}

home_status="$(request home "$SITE_ORIGIN/en")"
if [[ "$home_status" != "200" ]]; then
echo "Production homepage returned HTTP $home_status"
exit 1
fi
if ! grep --quiet --ignore-case "InterviewThread" "$response_dir/home.body"; then
echo "Production homepage did not contain the expected product marker"
exit 1
fi

health_status="$(request health "$SITE_ORIGIN/api/healthz")"
if [[ "$health_status" != "200" ]]; then
echo "Production health endpoint returned HTTP $health_status"
exit 1
fi
if ! jq --exit-status '.status == "ok" and (keys == ["status"])' \
"$response_dir/health.body" >/dev/null; then
echo "Production health endpoint returned an unexpected payload"
exit 1
fi
if ! grep --quiet --ignore-case '^cache-control:.*no-store' \
"$response_dir/health.headers"; then
echo "Production health endpoint is missing no-store caching"
exit 1
fi
65 changes: 35 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ practice while keeping genuine gaps visible. Beta testers are welcome.

**[Try InterviewThread](https://interviewthreadai.com/en)** · **[Join the founding beta](https://interviewthreadai.com/en/beta)** · **[Report a reproducible beta issue](https://github.com/weiyu1029/Interview_Thread_AI/issues/new?template=beta_feedback.yml)**

[Watch the 60-second walkthrough](https://interviewthreadai.com/interviewthread-60-second-walkthrough.mp4) · [Read the beta guide](docs/BETA_TESTING.md) · [Preview the first beta release](docs/releases/v0.1.0-beta.1.md) · [Contribute](CONTRIBUTING.md) · [Support](SUPPORT.md) · [Security](SECURITY.md)
[Watch the 60-second walkthrough](https://interviewthreadai.com/interviewthread-60-second-walkthrough.mp4) · [Read the beta guide](docs/BETA_TESTING.md) · [Production architecture](docs/platform_architecture.md) · [Operations runbook](docs/production_operations.md) · [Contribute](CONTRIBUTING.md) · [Security](SECURITY.md)

> **Founding beta testers wanted.** We are inviting new graduates, career
> changers, non-native English speakers, and candidates interviewing in the
Expand Down Expand Up @@ -95,32 +95,37 @@ and feature experiments. It supports:

## Production web platform

The `platform/` directory is the production-oriented evolution path for free
accounts, permanent tracking, and open-source collaboration:
`platform/web` is the formal production full-stack application. Its localized
React interface and same-origin backend routes ship as one Cloudflare Sites
Worker, and Cloudflare D1 stores account-backed records. This keeps the browser,
API and OAuth callbacks on one origin and makes every frontend/backend release
atomic and reversible.

- a professional, responsive, emoji-free React / Next-compatible interface;
- guest analysis, 40 locale choices with eight reviewed end-to-end catalogs
and 32 community-beta catalogs, locale-aware AI output, worldwide recommendation filters, an
interactive Market Insights preview, a device-local tracker, evidence-aware
copilot, and feedback;
- FastAPI endpoints for identity, workspaces, persisted analyses, tracker items,
evidence-ranked job recommendations, market snapshots, application-mode
policies, analysis-linked chat, feedback, model discovery, usage, and plans;
- PostgreSQL-ready multi-tenant data models and role-based workspace access;
- Docker Compose for the web, API, and PostgreSQL services;
- one free, open-source access level with no checkout or paid entitlement.
The production platform includes:

Start the complete local stack:
- responsive web and mobile interfaces with 40 locale choices;
- guest mode plus Google, GitHub and LinkedIn OAuth accounts;
- server-side document parsing, evidence mapping, job adapters and mock
interview APIs;
- authenticated Azure speech routes with device fallback;
- D1-backed activity, beta and feedback records;
- privacy-minimized structured logs, a D1 health endpoint, scheduled smoke
checks and a private aggregate-only operator dashboard;
- one open-source access level with no checkout or paid entitlement.

Run the production application locally:

```bash
cd platform
cp .env.example .env
# replace the legacy CAREERPROOF_JWT_SECRET compatibility variable before starting
docker compose up --build
cd platform/web
cp .dev.vars.example .dev.vars
npm install
npm run dev
```

The web client is available at `http://localhost:3000` and the documented API at
`http://localhost:8000/docs`.
The optional `platform/api` FastAPI/PostgreSQL project is retained for
self-hosting experiments. It is not called by `interviewthreadai.com` and is
not a second production backend. See the [production architecture](docs/platform_architecture.md)
and [operations runbook](docs/production_operations.md).

## Open and local model ecosystem

Expand Down Expand Up @@ -235,9 +240,9 @@ Interview_Thread_AI/
│ └── privacy.py
├── tests/ # deterministic matching and privacy tests
├── platform/
│ ├── web/ # professional public React interface
│ ├── api/ # FastAPI multi-tenant service
│ └── docker-compose.yml # web + API + PostgreSQL
│ ├── web/ # production Cloudflare full-stack app + D1
│ ├── api/ # optional FastAPI self-hosting prototype
│ └── docker-compose.yml # local prototype stack only
├── docs/
├── .github/ # CI, issue forms, dependency updates
├── streamlit_app.py # public web entry point
Expand Down Expand Up @@ -285,13 +290,13 @@ docker build -t interviewthread .
docker run --rm -p 8501:8501 interviewthread
```

### Multi-user platform
### Production platform

Use `platform/docker-compose.yml` for local evaluation. For public production,
use managed PostgreSQL, reviewed schema migrations, encrypted backups, a
rate-limiting proxy, and an asynchronous document queue before enabling open
registration. The Next.js workspace is the public product. The Streamlit
version remains a legacy reference implementation and feature incubator.
The public product is deployed from `platform/web` to Cloudflare Sites with D1,
reviewed environment configuration, immutable release versions, protected-branch
CI and rollback. `platform/docker-compose.yml` is for optional local prototype
evaluation only. The Streamlit version remains a legacy reference implementation
and feature incubator.

## Community maintenance

Expand Down
141 changes: 72 additions & 69 deletions docs/platform_architecture.md
Original file line number Diff line number Diff line change
@@ -1,87 +1,90 @@
# Platform Architecture
# Production Platform Architecture

InterviewThread uses a progressive architecture: anyone can start without an
account, while people who need permanent history or collaboration can move into
the free account-backed platform without changing the
evidence model.
InterviewThread is a real full-stack application deployed at
`interviewthreadai.com`. The production source of truth is `platform/web`.
Its React interface and same-origin API routes run together as a Cloudflare
Sites Worker, with Cloudflare D1 as the managed relational database.

The separate `platform/api` FastAPI/PostgreSQL project remains an optional
self-hosting prototype. The public website does not call it, and it must not be
treated as a second production backend.

## System boundary

```text
Next-compatible web client
├─ guest evidence match and device-local tracker
└─ authenticated API client
FastAPI application
├─ identity and workspace authorization
├─ document extraction and PII redaction
├─ canonical keyword and evidence engine
├─ model-provider router
├─ story and chat orchestration
├─ global job-provider adapters and evidence ranking
├─ market snapshot aggregation and provenance
├─ application-mode safety policy
├─ feedback and usage events
└─ open-source feature configuration
PostgreSQL
├─ users, workspaces, and memberships
├─ analyses and evidence-linked stories
├─ tracker items and conversations
├─ jobs, market metrics, and application preferences
├─ feedback
└─ usage events
Browser
│ HTTPS, Cloudflare TLS, WAF and edge controls
Cloudflare Sites Worker (`platform/web`)
├─ localized React / server-rendered pages
├─ OAuth callbacks and encrypted session cookies
├─ same-origin API routes and request validation
├─ in-memory document parsing and evidence analysis
├─ aggregate-only operational events
└─ fixed-host outbound provider adapters
│ │
▼ └─ Azure Speech / Resend / approved ATS hosts
Cloudflare D1
├─ accounts and hashed sessions
├─ user-requested activity history
├─ beta status and product feedback
└─ aggregate event counts
```

The deterministic evidence result is canonical. A language model can improve
organization and phrasing, but cannot create a supported claim or silently
change the underlying score.
This arrangement keeps browser, API and identity on one origin, removes a
public database endpoint, and lets one immutable release contain both frontend
and backend code. Cloudflare version history is the rollback boundary.

Live job data is adapter-based and administrator-configured. The current
implementation includes a fixed-host Adzuna adapter and storage contracts for
imported job postings and market snapshots. Provider coverage is displayed as
provider coverage, not as a census of the global labor market.
## Identity and access

## Identity and tenancy
- Guest mode is supported, but guest interview history is not saved.
- Google, GitHub and LinkedIn use OAuth; provider secrets stay in encrypted
production environment variables.
- Session tokens are random, stored only as hashes in D1 and sent in secure,
HTTP-only cookies.
- Paid speech endpoints require a signed-in account.
- The operator dashboard requires both a valid session and an exact email match
in `ADMIN_EMAILS`; non-operators receive a 404.

- Guest use is available for the first analysis and device-local tracking.
- Registration creates a personal workspace and an owner membership.
- Every persisted record belongs to a workspace.
- Owner, admin, member, and viewer roles provide the basis for team access.
- API authorization checks workspace membership instead of trusting IDs from
the browser.
## Data and privacy boundary

The current alpha accepts email and password credentials. Production should
add verified email, password reset, passkeys or a well-maintained identity
provider, session revocation, audit logging, and abuse controls before public
registration is opened.
Uploaded documents are parsed in memory. Raw resume files, job descriptions,
interview audio and transcripts are not copied into observability logs.
Product tracking is deliberately limited to bounded event names, counts,
status codes, latency, provider category, release ID and random request ID.

## Data lifecycle
The public health endpoint runs `SELECT 1` against D1 and returns only
`{"status":"ok"}` or `{"status":"unavailable"}`. It never reveals schema,
provider errors, account data or infrastructure credentials.

Uploaded files are parsed in memory. The API stores redacted text and derived
analysis only when an authenticated user asks to persist the result. Raw file
storage is deliberately absent from the alpha. If original-file storage is
introduced, it should use encrypted object storage, short-lived upload URLs,
malware scanning, explicit retention controls, and per-workspace deletion.
Original-file object storage is disabled. If it is ever introduced, require a
separate threat model, malware scanning, short-lived upload URLs, retention and
deletion controls, and encryption-at-rest review before production use.

Model keys arrive through `X-Model-Api-Key` and are not written to the database.
Long-lived bring-your-own-key storage should not be added without envelope
encryption, key rotation, access auditing, and a clear deletion flow.
## Abuse and provider boundary

## Scale path
- State-changing JSON routes require an exact same-origin request, the correct
media type and both declared and actual request-size limits.
- Speech-to-text validates authentication, locale, audio type and size. A local
user window is a second layer; global limits belong at the Cloudflare edge.
- Text-to-speech is authenticated and falls back to device speech when the
managed provider is unavailable.
- Contact delivery uses a honeypot, bounded fields, a fixed recipient map and a
server-side Resend key. User input cannot choose arbitrary recipients.
- Job adapters may call only documented, fixed provider hosts.

1. Keep synchronous extraction and analysis while traffic is low.
2. Add a queue for OCR, large documents, and batch analyses.
3. Add Redis only when distributed rate limits, job locks, or short-lived caches
are actually required.
4. Add managed object storage only for features that require original files.
5. Split services by operating need, not by feature count.
## Monitoring and release

## Open-source access boundary
- `/api/healthz` verifies Worker-to-D1 health and disables caching.
- A scheduled GitHub Actions smoke check requests the English landing page and
health endpoint every 15 minutes without credentials.
- Worker logs are structured and privacy-minimized; the private operator page
exposes aggregate counts only.
- Production variables are managed through Sites. Secrets never enter source,
logs, build artifacts or the browser bundle.
- Releases are built from the reviewed Git commit, saved as an immutable Sites
version, deployed, smoke-tested and rolled back to the previous version if a
release gate fails.

The public product exposes one free access level. Workspace roles protect data
and collaboration boundaries, not commercial entitlements. The evidence engine,
self-hosting path, data export, application modes, and safety rules remain open
source and are never restricted by account status.
See [Production Operations](production_operations.md) for the release,
incident and recovery checklist.
Loading
Loading