Skip to content

Launch prep: production-ready backend, security fixes, truthful web app, university admin service - #8

Draft
NiksheyYadav wants to merge 106 commits into
mainfrom
claude/blissful-darwin-2n33wv
Draft

NiksheyYadav wants to merge 106 commits into
mainfrom
claude/blissful-darwin-2n33wv

Conversation

@NiksheyYadav

@NiksheyYadav NiksheyYadav commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

Integration branch for the 2026-11-17 launch (individuals plus one university pilot). It started as job-aware resume optimization and grew into the launch-readiness work. Owner-only steps are in docs/16_launch_checklist.md; status is in docs/15_progress.md.

Works on Vercel now

  • Every service exports a serverless handler (createVercelHandler); only auth did before.
  • Cross-service events no longer disappear: a Postgres outbox (MESSAGING_DRIVER=postgres, the default on Vercel) with drain endpoints protected by CRON_SECRET, driven by a scheduled GitHub workflow (Vercel Hobby has no sub-daily cron). Consumers are idempotent under at-least-once delivery.
  • Rate limiting actually enforces in all services (it was inert: the plugin was never awaited and routes sat outside its scope).
  • Durable resume storage via a Supabase Storage driver; timestamps are strict ISO 8601 (Safari could not parse Postgres-style ones).
  • CI runs the DB-backed tests against Postgres, checks bundle freshness, and runs a non-blocking dependency audit; vulnerable next, sharp, zod and source-map-js are patched.

Jobs

  • The catalog fills itself: scheduled ingestion from public Greenhouse, Lever and Ashby job boards (job_sources table, migration 0004, platform-admin /v1/jobs/sources, hourly ingest-jobs.yml). Postings a board stops listing are deactivated and no longer recommended.
  • Job list, detail and recommendations now name the employer.

Security

  • Signup metadata can no longer mint an organization or owner (privilege escalation). Global-data actions (job ingest and sources, learning metrics, contact creation) need a PLATFORM_ADMIN_USER_IDS allowlist.
  • rls.sql closes the Supabase Data API: RLS on every public table and no anon/authenticated grants.
  • http(s)-only stored URLs, uuid validation (a malformed id is 400, not 500), upload content sniffing and a DOCX zip-bomb guard, no raw DB errors in responses.

Product honesty

  • Apply is assisted: Greenhouse and Lever accept applications only with the employer's API key, so mode: auto is rejected unless APPLICATIONS_AUTO_MODE_ENABLED=true. The web app has a job page, a resume check against the job, and an assisted-apply panel ("Atlas does not submit applications for you").
  • Outreach is drafts only: send/schedule answer 503 unless OUTREACH_DELIVERY_ENABLED=true (nothing delivers email); users copy a draft, send it themselves and press "I sent this".
  • Web app: removed fabricated customer logos and fake demo and the placeholder Connectors page; email-confirmation, forgot/reset password and same-origin-only redirects.

Privacy

  • GET /v1/auth/me/export downloads everything held about the user; DELETE /v1/auth/me ({"confirm":"DELETE"}) deletes the account and its data in one transaction and then removes resume files. Both are in the web app under "Your data".

University pilot

  • admin-service: org-scoped roster list and CSV import, funnel, CSV export (formula-injection safe), audit log; authorization comes from organization_members in the database, non-members get 404.
  • apps/admin is now the staff console: organization chooser, roster with search and export, CSV import with per-row results, funnel.

Resume optimization (the original change): POST /v1/resumes/:id/optimize, advisory and stateless; it never rewrites resume content.

Verification

  • pnpm typecheck and eslint clean; 118 test files / 1,704 tests pass against Postgres 16 with the real migrations, auth-hooks.sql and rls.sql.
  • main was merged into the branch (it had gained a sign-in error fix and a progress report); the two web files that conflicted were resolved keeping this branch's confirm-email and password-reset flows plus main's friendlier error messages.
  • GitHub Actions on the head 43c5972: backend (typecheck, lint, DB tests, bundle freshness), web and admin builds, and the dependency audit are all green. The admin build failed once on this commit with next/font/google failing to resolve (a Google Fonts fetch during next build); the commit only changes a doc and the smoke script, and the single re-run passed with no change. The earlier merge commit 02e25b9 was green first time. If this recurs, make the admin and web builds independent of Google Fonts (next/font/local).
  • next build for apps/web and apps/admin verified locally.
  • First live check. The Vercel deployment quota cleared and the previews of 02e25b9 built. scripts/smoke.mjs against the atlas-backend and atlas-web previews: 15 passed, 5 warnings, 0 failed. All nine /v1 services answer 401 from the service itself (the committed bundles boot and the rewrites route), the web pages render, and CORS allows the web origin. The five warnings are CRON_SECRET not being set on that preview.
  • What that does not prove: a 401 comes before any database or Supabase call, so sign-in, uploads, matching, ingestion and account deletion are still unverified against real Supabase (the project was paused when last checked).
  • Vercel status: atlas-backend, atlas-admin, atlas-learning, atlas-profile and atlas-referrals deployed on 02e25b9. atlas-jobs (a superseded per-service project) failed to build there; its log is not reachable from the build session (npx vercel inspect dpl_95uCaJRPMJ5m7CJjnxxuVpBXFjpQ --logs), and the same jobs code is deployed and answering in atlas-backend. Each push builds twelve Vercel projects, so the daily quota is exhausted again within a few pushes (atlas-applications and atlas-profile were rate limited on 43c5972).

Needs the owner before launch

  • Confirm the apply model: the earlier "auto-submit on a few ATSs" choice is not possible without employer-held keys, so launch is assisted apply.
  • Restore Supabase and apply auth-hooks.sql (re-apply) then rls.sql; run pnpm db:migrate for 0004.
  • Set env vars (CRON_SECRET on jobs too, PLATFORM_ADMIN_USER_IDS, storage keys on profile and auth) and the GitHub variables and secret for the drain and job-ingest workflows.
  • Delete the nine superseded per-service Vercel projects (the checklist and docs/15_progress_report.md agree); add the first job-board sources and the pilot organization.
  • Terms of Service, the privacy review, and a staging check of account deletion. Details and order in the checklist.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1

Adds the missing resume-optimization step from the primary flow
(docs/01 step 5) as the smallest safe version: deterministic, advisory,
and stateless.

- @atlas/ai: analyzeResumeForJob compares a parsed resume with one job's
  keywords (explicit skills plus vocabulary terms in title/description),
  flags weak bullets (duty-style openers, no measurable result, too
  short/long), and folds in the resume-level ATS hints. It never writes
  resume text or asserts a skill/metric the candidate did not state.
- @atlas/ai: expose containsKeyword so non-vocabulary job skills get the
  same boundary-aware match as extractSkillKeywords (now built on it).
- @atlas/types: report schema and optimize request/response contracts.
- profile-service: POST /v1/resumes/:id/optimize. Owner-scoped (404 for
  other users' resumes), 409 for unparsed resumes, 404 for unknown jobs,
  400 on malformed ids/bodies. Reads jobs read-only, as
  applications-service and matching-service already do.
- docs: document the endpoint (docs/04) and add docs/15 progress report
  with the verified baseline and known gaps.
- api/: regenerate committed bundles (profile gains the route; the rest
  gain only the new shared schemas; jobs picks up the skills refactor).

Verified: typecheck and lint clean; 183 tests pass (18 new) with the
DB-backed suites run against Postgres 16 + pgvector.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
@vercel

vercel Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
atlas-admin Ready Ready Preview Oct 29, 2026 1:19pm UTC
atlas-analytics Ready Ready Preview Oct 29, 2026 1:19pm UTC
atlas-applications Ready Ready Preview Oct 29, 2026 1:19pm UTC
atlas-auth Ready Ready Preview Oct 29, 2026 1:19pm UTC
atlas-backend Ready Ready Preview Oct 29, 2026 1:19pm UTC
atlas-jobs Error Error Oct 29, 2026 1:19pm UTC
atlas-learning Ready Ready Preview Oct 29, 2026 1:19pm UTC
atlas-matching Ready Ready Preview Oct 29, 2026 1:19pm UTC
atlas-outreach Ready Ready Preview Oct 29, 2026 1:19pm UTC
atlas-profile Ready Ready Preview Oct 29, 2026 1:19pm UTC
atlas-referrals Ready Ready Preview Oct 29, 2026 1:19pm UTC
atlas-web Ready Ready Preview Oct 29, 2026 1:19pm UTC

claude added 11 commits October 27, 2026 12:00
createVercelHandler(app) is the dispatch logic services/auth/src/index.ts
inlines today: await Fastify's ready(), then emit the Node request/response
pair onto app.server. It takes a structural type so @atlas/utils does not
gain a fastify dependency, starts plugin boot eagerly so it overlaps
cold-start init, and guards that eager promise so a boot failure surfaces
on each invocation rather than as an unhandled rejection at cold start.

withServerlessEnvDefaults() feeds the services' env loaders a placeholder
PORT when VERCEL is set. packages/config's httpEnvSchema requires PORT with
no default, but a function never binds a port, so without it every module
throws a ConfigError at load. Outside Vercel the source is returned
untouched, so local dev and tests keep strict validation, and nothing but
PORT is defaulted.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
These entrypoints wrapped startup in main() and only ever called
app.listen(), with no default export, so as Vercel functions (see
e886019 for the failure mode on auth) they could not serve a request.

Convert them to the auth pattern: construct env/logger/db/broker at module
scope, export `app`, export the default handler from createVercelHandler,
and install shutdown handlers and listen() only when VERCEL is unset, so
`pnpm dev:<service>` behaves as before. Env loading goes through
withServerlessEnvDefaults() so a missing PORT does not fail module load on
Vercel.

Each service gets an entrypoint test that loads the module with VERCEL=1
and no PORT, asserts the default export is a function and that nothing
binds a socket, serves /health through the handler (503 with an
unreachable database, 200 with ATLAS_TEST_DATABASE_URL), checks that other
missing config still fails fast, and checks that local mode still listens.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
…andler

Same conversion as the previous commit for the services that also run an
event consumer. registerConsumers() still runs in every runtime, now via
top-level await after the app is built, so a failed subscription fails
module load loudly like a bad config does; subscriptions are kept for the
local shutdown path, which stops them before closing the app, as before.

The entrypoint tests additionally assert the consumers are still
registered when the module is loaded with VERCEL=1.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
The DB-backed integration suites need more than a bare Postgres: the tests insert into Supabase's auth.users, which fires handle_new_user from packages/db/supabase/auth-hooks.sql, and that file references the supabase_auth_admin, authenticated and anon roles.

infra/ci/supabase-stub.sql provides just those pieces (NOLOGIN roles, schema auth, and an auth.users table with the columns the tests and the trigger use). It is idempotent and refuses to run if the database looks like a real Supabase project (auth.schema_migrations or auth.identities present).

scripts/ci/setup-test-db.sh runs the whole recipe against DATABASE_URL: the stub, pnpm db:migrate, then auth-hooks.sql. It is re-runnable: auth-hooks.sql adds a foreign key without IF NOT EXISTS, so the script drops that constraint first. CI and developers use the same script.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
The backend job had no Postgres, so 10 test files (58 tests) self-skipped and still reported green. Add a pgvector/pgvector:pg17 service container (same image as docker-compose.yml) with a TCP health check, prepare it with scripts/ci/setup-test-db.sh, and set ATLAS_TEST_DATABASE_URL so the suites actually run. The test step is renamed and the stale 'skipped' comment removed.

Also set workflow-level permissions to contents: read and a 20 minute job timeout. The database credentials are throwaway values for an ephemeral service container, not secrets.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
Vercel deploys the committed api/*/index.js bundles, not the output of a build step (see the header of scripts/bundle-backend.mjs), so a source or packages/* change that is merged without re-bundling ships old code. Rebuild in CI and fail if the result differs from what is committed.

New bundles are registered with git add --intent-to-add first: a plain git diff --exit-code does not see untracked files, so a newly added service would pass unnoticed. The failure message says to run node scripts/bundle-backend.mjs and commit api/.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
Dependabot: weekly npm (pnpm workspace root) and github-actions updates. Minor and patch bumps are grouped per dependency type; majors open their own PRs. Open-PR limits are 5 and 3, and commit messages use conventional prefixes.

Audit: a separate job runs pnpm audit --prod --audit-level=high with continue-on-error, so it reports without gating. It reads only the lockfile and needs no install. It does exit non-zero today (3 critical and 5 high advisories on production dependencies), which is why it must not block the build.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
… first run)

Update the Verified baseline bullets and the Known gaps CI bullet: CI now provisions Postgres and sets ATLAS_TEST_DATABASE_URL, and also checks bundle freshness and runs an advisory audit. All of it is marked pending first CI run because it was rehearsed locally on Postgres 16 and has not been observed in GitHub Actions. Skip counts corrected to the measured 10 files / 58 tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
services/auth called loadAuthServiceEnv() with no serverless default, so on
Vercel (which provides no PORT) it threw a ConfigError at module load unless
PORT was set by hand in the project env. Use withServerlessEnvDefaults() like
the other services and replace the inline handler with the shared
createVercelHandler, which also guards a failed boot against an unhandled
rejection at cold start.

Adds the service's first test, an entrypoint test mirroring the other
services (loads without PORT, exports a handler, serves /health, local mode
still listens). Removing the PORT default fails 4 of its 5 cases.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
Rebuild api/*/index.js with scripts/bundle-backend.mjs so the deployed
functions carry the new Vercel handler exports (createVercelHandler,
withServerlessEnvDefaults) for all nine services.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
claude added 10 commits October 27, 2026 13:48
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
The job page needs to know whether the signed-in user already has an
application for a job; without a filter it had to walk up to five pages of 100.
GET /v1/applications?job_id=<uuid> restricts the caller's own list. It is
rejected (400) with scope=organization, because a per-job filter would let
university staff ask who applied to a given job, which is exactly the
job-level detail staff must not see.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
…ages page

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
…anagement

POST|GET /internal/jobs/ingest-sources, secured by a CRON_SECRET bearer exactly
like the drain routes (constant-time compare, 503 when unset) and registered
outside the rate limiter. It reads enabled job_sources least-recently-fetched
first, a few at a time, inside a wall-clock budget (default 20s of the 30s
function limit). A source that fails is recorded (short, fixed, scrubbed
message in last_error) and skipped; the run carries on. Known listings are
settled with a statement per few hundred postings, so a steady-state re-read of
a 700-posting board is ~300ms locally and a second run inserts nothing.

Postings a successfully fetched source no longer lists are marked inactive,
scoped to that provider and company and judged by last_seen_at against the
database clock. Nothing is deactivated for an empty result, an incomplete run,
a run with postings that could not be stored, or a sharp drop from a sizeable
board (applied one run later if it is real). A failed fetch never reaches the
sweep.

GET and POST /v1/jobs/sources let platform administrators list, add, enable
and disable boards; board tokens are validated to a URL-safe charset, and two
boards of one provider cannot share a company name.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
…, funnel and paging helpers

- api.ts: ApiError carries request id, details and Retry-After; non-JSON and
  empty error bodies no longer throw; network failures become a typed error.
- errors.ts: describeApiError maps every status the roster API can answer
  (401/403/404/409/413/415/429/5xx, network, bad response) to vetted copy and a
  recovery action, never echoing server text.
- contracts.ts: runtime mirror of packages/types/src/admin.ts. Turbopack cannot
  resolve @atlas/types' NodeNext .js specifiers at runtime, so the schemas are
  copied here, guarded by compile-time assignability checks and a parity test.
- csv.ts/import-csv.ts: client pre-flight of a roster file (size, type, UTF-8,
  header, 5000-row cap) using a port of the server's reader, the template, row
  error wording and an honest import summary. csv-parity.test.ts runs the
  client and server readers over the same inputs.
- Pagination cursor stack, roster/funnel query builders, safe next-path,
  filename sanitising. 209 tests under apps/admin/test.
- Drop the analytics and org-wide applications service modules: the new
  console does not use them (see the dashboard commit).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
…ata moved out of the console

- Guard sends signed-out visitors to /login?next=<path> and returns them there
  through a same-origin-only redirect check.
- /dashboard resolves the organization from the sign-in claim (the user's
  oldest membership, the only thing /v1/auth/me exposes); an owner/admin claim
  opens it, anything else lands on a chooser that says plainly when the account
  is not a member of a university organization. An organization id can also be
  pasted; the API still authorizes it (404 non-member, 403 wrong role/type).
- Matching-model health (learning-service, platform-admin only) is now a
  labelled platform-wide page that degrades to an explanation on 403 instead of
  sitting in the university console. The old dashboard's raw application list
  and event counts are gone: they were keyed to the token's organization, not
  the selected one, and listed opaque ids.
- Privacy hardening: no-store/no-frame/no-referrer headers on pages, noindex,
  and the error boundary logs only the error name and digest.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
…and CSV export

Search and filters live in component state only (never the URL or browser
storage: a search term is often an email). Pagination is forward-only as the API
is; a cursor stack provides Previous. Students who are not members of the
organization show a dash plus screen-reader text rather than zeros. Export
fetches the roster with the bearer token and saves it as a blob under a
sanitised server-provided file name.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
… and a truthful summary

File type, size, UTF-8 and structure (header, 5000-row cap, quoting) are checked
in the browser before upload; the file is sent as text/csv with an
Idempotency-Key that is reused only for retries of the same file. The result
lists every outcome bucket with the API's own counts, groups and lists row
errors without echoing submitted values, and states plainly that rows without an
Atlas account are skipped and nobody is emailed or invited.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
The funnel lists the API's counts per stage as an accessible table with
decorative bars, with the activity window and the as-of-now/in-window basis
stated per row; no percentages or other numbers are derived. The audit log page
says it is unavailable (admin-service writes entries but exposes no read
endpoint) and describes only what is recorded today.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
… and outreach honesty

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
@vercel

vercel Bot commented Oct 27, 2026

Copy link
Copy Markdown

Deployment failed for project atlas-analytics with the following error:

Resource is limited - try again in 1 day (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/niksheyyadavs-projects?upgradeToPro=build-rate-limit

… and docs

.github/workflows/ingest-jobs.yml calls POST /internal/jobs/ingest-sources
hourly (and on workflow_dispatch, optionally for one source_id). Like
drain-events.yml it is inert until the ATLAS_API_BASE_URL variable and the
CRON_SECRET secret exist; an optional ATLAS_JOBS_URL variable overrides the
base URL when the jobs service is its own Vercel project. The route itself is
served by the jobs Fastify app, and vercel.json gains the
/internal/jobs/:path* rewrite for the single-project layout.

packages/db/seed/job-sources.example.sql seeds a handful of real public boards
(tokens verified against the live endpoints on 2026-10-27) with ON CONFLICT DO
NOTHING; a test pins that it applies cleanly, twice, and only inserts. docs/04
documents the endpoints, what is read, and exactly when postings are
deactivated. .env.example gains JOB_SOURCE_CONTACT and the CRON_SECRET note.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
…r deleting a user

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
…nt export/delete

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
The connectors service is not deployed and is optional per project rules, so the
nav no longer links to a page that only says it is being surveyed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
The company contacts route exists in the referrals service but the
single-project layout had no rewrite for it, so it answered 404.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
…/delete

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1

Copy link
Copy Markdown
Owner Author

The twelve Vercel – atlas-* statuses are red for a quota reason, not because of this PR's code.

Each one reports Deployment rate limited — retry in 24 hours (api-deployments-free-per-day, more than 100). The Vercel free plan allows 100 deployments per day for the whole team, and this repository is connected to twelve Vercel projects that all build on every push: atlas-backend, atlas-web, atlas-admin, and nine per-service projects (atlas-auth, -profile, -jobs, -matching, -applications, -referrals, -outreach, -learning, -analytics). One push is therefore twelve deployments, so about eight pushes use the whole day.

  • What passes: the GitHub Actions checks (backend typecheck/lint/tests, both frontends, dependency audit) are separate and are what verify the code. They are running on bb888aa.
  • Nothing in the repo can fix it: a re-run would hit the same limit, and the Vercel connector in this session cannot see these projects, so I cannot re-trigger or disconnect them.
  • What the owner needs to do: keep atlas-backend, atlas-web and atlas-admin and disconnect the nine per-service projects (that makes a push three deployments), or move to a paid plan. Hobby is also meant for non-commercial use, which matters for a launch with a university customer. Details are in docs/16_launch_checklist.md section 2.
  • Until then: I am batching pushes to avoid burning more of the allowance. The statuses should clear once the 24-hour window passes or the project count drops.

Generated by Claude Code

…and the HTML stripper

Recommendations read precomputed scores joined to jobs, so a posting its
source stopped listing (now marked inactive by scheduled ingestion) would have
kept appearing in people's recommendations. listForUser now excludes inactive
jobs, with a test.

Adds direct tests for the shared ingestPosting(): outcomes, a burst of
identical postings settling to one insert with no error (the unique-violation
race handler moved here from the route and had no test), the edit-in-place
opt-in, and the caller-resolved company. Mutation checks showed the HTML
stripper's linear-time tests were too small to fail a quadratic version, so
the hostile inputs are now sized to take seconds if the cutoffs regress.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
Resolves docs/04_api_design.md (outreach and job-source sections kept) and
documents the assisted-apply application endpoints.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
The catalog now fills from job boards, so a job card with only a title and no
employer is not usable. company_name is optional in the shared Job type; the
jobs service fills it on every read, and the web list and detail pages show it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
…ngest route; rebundle

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1
@vercel

vercel Bot commented Oct 27, 2026

Copy link
Copy Markdown

Deployment failed for project atlas-jobs with the following error:

Resource is limited - try again in 1 day (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/niksheyyadavs-projects?upgradeToPro=build-rate-limit

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1

# Conflicts:
#	apps/web/src/app/register/page.tsx
#	apps/web/src/lib/auth-context.tsx
…ng correctly

The smoke script ran against the Vercel preview of 02e25b9: 15 passed,
5 warnings (CRON_SECRET unset), 0 failed. The progress report now says what
that does and does not prove, and notes the atlas-jobs per-service failure.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1

Copy link
Copy Markdown
Owner Author

Vercel – atlas-jobs fails to build, and I cannot find the cause from here.

  • What fails: the per-service Vercel project atlas-jobs (Root Directory services/jobs) failed on 02e25b9 and again on 43c5972 (Deployment has failed). It deployed fine on earlier commits of this PR, before stream P (scheduled job-board ingestion) was merged, so the timing points at that change. That is a correlation only.
  • Why I can't tell: the build log is behind your Vercel login. The Vercel connector in my session returns 404 for these deployments (a different account scope), so I can't read it or re-run the build. The log is one command: npx vercel inspect dpl_iTCNFRXM5YGHRbgF3hLU8jRNvgzt --logs.
  • What I checked: services/jobs type-checks on its own with its own tsconfig, all 253 of its tests pass against Postgres, and the env variables stream P added are optional or defaulted, so nothing new is required at import time. The same jobs code is in the atlas-backend single-project deployment, where the smoke script gets a JSON 401 from /v1/jobs, and the job-ingest route answers 503 as designed because CRON_SECRET is not set on that preview. The other per-service projects (atlas-learning, atlas-profile, atlas-referrals, atlas-auth, atlas-matching, atlas-web) built.
  • Why it may not matter: this is one of the nine per-service projects that docs/15_progress_report.md on main and docs/16_launch_checklist.md already say to delete in favour of atlas-backend, and the repo has no per-service build configuration, so those projects depend on dashboard settings I can't see.
  • What would help if you want to keep the per-service layout: paste the last 30 lines of that log here and I will fix it.

Generated by Claude Code

Assisted apply is the launch model, confirmed by the owner. Automatic submission
stays off until an employer-supplied ATS key and a submission worker exist.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdqVHQRXj76uudaef6gTD1

This branch had an error being deployed

1 failed and 11 active deployments
Preview – atlas-matching — 27c8465b Deployed Oct 29, 2026 by vercel[bot]
Preview – atlas-auth — 27c8465b Deployed Oct 29, 2026 by vercel[bot]
Preview – atlas-outreach — 27c8465b Deployed Oct 29, 2026 by vercel[bot]
Preview – atlas-backend — 27c8465b Deployed Oct 29, 2026 by vercel[bot]
Preview – atlas-analytics — 27c8465b Deployed Oct 29, 2026 by vercel[bot]
Preview – atlas-profile — 27c8465b Deployed Oct 29, 2026 by vercel[bot]
Preview – atlas-jobs — 27c8465b Deployed Oct 29, 2026 by vercel[bot]
Preview – atlas-learning — 27c8465b Deployed Oct 29, 2026 by vercel[bot]
Preview – atlas-referrals — 27c8465b Deployed Oct 29, 2026 by vercel[bot]
Preview – atlas-applications — 27c8465b Deployed Oct 29, 2026 by vercel[bot]
Preview – atlas-web — 27c8465b Deployed Oct 29, 2026 by vercel[bot]
Preview – atlas-admin — 27c8465b Deployed Oct 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants