System Architecture
There are two products and three privacy/runtime zones. JobCache App is the observer-facing product edge. JobCache Core is JobCache's shared corpus, search, recommendation and market-intelligence substrate. CareerVector is the person-facing private workspace product. JobCache Core belongs operationally and conceptually to JobCache, while owning neither product's local identities.
Status (2026-08-18): the three-zone boundary is live. The processing plane shown below
is the target evolution: a small, rebuildable jobcache-pipeline D1 coordinates
content-addressed enrichment and batched publication without becoming a fourth fact-tree
rung or another source of public truth. See The Shared Seam Package
for the exact as-built privacy boundary.
[!important] Whole-system blueprint Open the code-rendered Blueprint for the canonical 5x4 operating map. Every region links to its own Markdown contract in the repository-level
architecture/folder. The diagram below remains the detailed ingestion and privacy projection.
The main public-data lifecycle runs left to right. Product queries and immutable result delivery attach vertically through the Core APIs. The dashed management plane is deliberately the next design pass; it observes the system sideways and emits declared control intent, but it must not become a second scheduler or corpus writer.
Detailed privacy-boundary diagram
Reading the detailed privacy diagram
| Mark | Means |
|---|---|
| Teal outline | transforms data — pipelines and boundary modules |
| Red outline | privacy-critical — authenticated internal product protocol, never a public product route |
| Heavy outline | must be reachable — the public API |
| Thin outline | a perspective — a facade on the public API |
| Grey fill | a store — it holds, it does not transform |
| Solid arrow | slow lane — batch, pulled by JobCache Core, nobody is waiting |
| Dashed arrow | fast lane — synchronous; never routes the response through a store |
| Red dashed rule | a privacy seam. No product-local identity crosses it |
The claims it makes
Two products, three zones
CareerVector is the private applicant domain. JobCache contains two runtime zones: JobCache App is its observer-facing private edge, while JobCache Core owns the shared market corpus and intelligence. “Core” describes a concern boundary inside JobCache, not a third product, brand or neutral owner.
Two privacy seams, with two permitted privacy classes
Each product edge keeps the only map from its local identity: workspace_id ↔ person_key
in CareerVector and session_id ↔ observer_key in JobCache App. A fact crossing a seam
declares exactly one privacy class:
| Class | Used for | Crossing rule |
|---|---|---|
| pseudonymous individual | recommendation, individualized saved state | a versioned person_key or observer_key; never the local ID |
| anonymous aggregate | market metrics and cohort analytics | no participant key; thresholded before emission |
Pseudonyms remain personal, linkable data and receive retention, erasure and key-rotation
handling. Anonymous is a property of the emitted aggregate, not a synonym for “hashed.”
Only person facts may train person recommendations; observer activity must never be read
as applicant demand.
The mirror is implementation, not duplicated design
The outer zones have the same store/reduction, collector and internal-API mechanism. The
participant noun selects a coherent profile (person or observer); product public APIs
and perspectives remain distinct.
| Row | Shared implementation? |
|---|---|
| perspectives | no — RADAR and JobCache browse/dashboard answer different questions |
| public API | no — product-specific stateless reads and workspace/session-capability operations |
| local store + reduction | yes |
| fast and slow collectors | yes |
| seam-facing internal API | yes |
The exact package boundary and current extraction status live in The Shared Seam Package.
Fast and slow are different reliability contracts
Stateless JobCache search/evidence returns the verified JobCache Core answer without creating local identity or state. A fast operation that promises reloadable workspace/session state also returns Core's answer directly, but commits the immutable result snapshot and local pointer atomically before reporting success. A stored snapshot is the fallback floor. On first use, or where no compatible snapshot exists, an outage is honestly unavailable rather than silently described as stale.
The slow lane is claim/ack work pulled by JobCache Core. A cursor advances only for facts durably accepted by Core; rejected facts remain visible in a quarantine path. Delivery back to a product edge is idempotent and commits its snapshot and pointer atomically. Each worker is authenticated by name and receives only its declared operation capabilities; the general cross-product control token cannot enter this all-workspace protocol.
JobCache Core owns corpus state, not edge-operational churn
CrateDB owns shared ads, roles, evidence, embeddings and graph projections. Session or workspace state remains at the relevant product edge. Leases, retries, delivery receipts and quarantine are operational state beside the JobCache internal API, not search-index tenants in the corpus.
Result identity is immutable
A request-equivalence key may deduplicate equal work, but it is not a result ID. A result ID identifies canonical ordered bytes plus the corpus snapshot, retrieval/ranking version and other result-changing inputs. The same result ID can never name different bytes. Provenance, persistence, age and health are separate fields; a stored result is not automatically stale.
Current implementation state
| Zone | As built | Gap to this graph |
|---|---|---|
| CareerVector | D1-backed RADAR routes, verified role search/delivery, composite slow-lane cursors and atomic participant-erasure outbox | deploy the additive migration/credentials, run the Core erasure worker and finish the remaining recommendation/metrics cutover |
| JobCache Core | live corpus/extraction, strict role search and fail-closed participant tombstones with deterministic receipts | deploy the lifecycle migration/credential; keep reducers on active views and finish recommendation/metrics APIs |
| JobCache App | jobcache/api/ D1 API with opaque sessions, views, immutable snapshot grants, explicit reductions and deletion outbox; jobcache/mcp/ thin peer |
provision App D1 and Workers in canonical IaC, coordinate domain/service binding cutover, then build UI as another API client |
The direct-CrateDB and JobCache-public search gateways in CareerVector are compatibility scaffolds, not the prescribed boundary. The target state is CareerVector calling only the authenticated JobCache internal API.
Vocabulary
| Term | Means |
|---|---|
| Ad | one posting on one board — evidence for a role |
| Role | one hiring intent backed by one or more ads — the user-facing unit |
| Person | someone in the market; pseudonymized as person_key when individual context is required |
| Observer | someone looking at the market; pseudonymized as observer_key when individual context is required |
| Profile | the person a CareerVector workspace serves; never a list |
| Search results | roles returned for an active query |
| Recommendations | roles materialized from a person's reduced preference state |
“Candidates” is not used: in a hiring product it means applicants.
Deliberately off this map
Ad discovery and extraction are separate JobCache Core pipelines. CareerVector URL import and JobCache submission may both contribute public ad evidence, but neither changes the privacy rules for individual or aggregate participant facts. Ad ownership is likewise not implied: an observer may analyze any public ad.
CareerVector's private LLM routing is an independent Sporewright application documented in LLM-TENSOR-ROUTING. Sporewright owns generic routing mathematics; it does not own this seam, either product's identities, or JobCache corpus persistence.