Free-Tier Infrastructure
JobCache is a peer product, not a hosted SaaS. It pays for itself by operating inside the free tier of every vendor it uses. That constraint shapes a lot of decisions that look strange in isolation.
The free-tier inventory
| Layer | Vendor | Free-tier ceiling |
|---|---|---|
| Hot DB | CrateDB Cloud (CRFREE) | 2 vCPU / 2 GiB / 8 GiB disk, single node, no request meter. Replaced CockroachDB Serverless in the 2026-06 cutover; the Cockroach cluster is dormant, rollback only. |
| Service hosting | Render Hobby | Small monthly compute budget, sleep when idle |
| Edge workers | Cloudflare Workers (free) | 100k requests/day account-wide |
| Cold evidence | Backblaze B2 | 10 GB stored, 1 GB/day download free |
| Scheduler | CircleCI self-hosted runner | resource_class: corbet/unraid; work runs on owned k3s/Unraid capacity |
| Planner tick | Hourly CircleCI jobcache_planner → Render interface |
Bearer-token gated; queues device work only |
Pulumi tracks the inventory in jobcache/infra/ so the stack can be torn down and rebuilt without rediscovering which vendor holds what.
Peer-product, not sub-product
CareerVector and JobCache share the objective public ad boundary: ad_id, shared field vocabulary, cells, evidence keys, chunks, embeddings, and current-cell rules. They do not share a deploy unit or an auth surface. JobCache can be rebranded, sold, or operated for a third party without dragging CareerVector along; CareerVector can switch shared-ad providers without JobCache going dark.
The CareerVector ↔ JobCache contract is the shared ad/field/cell model in CrateDB plus operational read/control APIs around it. CareerVector's bottom-up workspace lookup/write and JobCache's top-down distributed ingest both use that boundary; private workspace jobs are not part of the submitted observation envelope. This split is by design — see JOBCACHE-CAREERVECTOR-RELATIONSHIP.md for the broader peer-product reasoning.
MCP consolidation
JobCache hosts multiple MCP surfaces (mcp.jobcache.corbet.ch, mcp.ops.jobcache.corbet.ch, mcp.status.jobcache.corbet.ch, mcp.qa.jobcache.corbet.ch). They are deliberately not one mega-worker:
- Each MCP wraps exactly one API. Auth posture lines up with the API it fronts (public, admin, public, allowlisted).
- A leak in one cannot reach the data plane of another.
- The free-tier Worker request budget is shared, but each MCP's surface is small and operator-only or agent-only — total traffic is tiny.
What is consolidated is the secret: all admin MCPs use the single rotated ADMIN_TOKEN. See admin-auth.md.
The 100k-account-wide footgun
Cloudflare Workers free tier is 100k requests/day across every Worker on the account. JobCache shares this bucket with CareerVector and dotkeeper. CLAUDE.md §22 governs the rule: no push-triggered CI that hits deployed Workers, no cron loops, no high-fanout poll. Audit the daily total with ~/.agent/tools/cf-quota/check.mjs before adding any new automation.
This is why the JobCache planner job lives in CircleCI on corbet/unraid and
hits the Render-hosted interface, not a Cloudflare Worker. The job queues
bounded device work; it does not scrape, chunk, embed, geocode, or run LLMs
centrally.
See also
jobcache/wiki/content/architecture/README.md— stack overview.wiki/content/architecture/JOBCACHE-CAREERVECTOR-RELATIONSHIP.md— peer-product split.- [[code:jobcache/infra/README.md]] — Pulumi inventory.