Desktop Edge Runtime Proposal

Jobcache should treat the desktop app as an optional edge runtime, not as a second product. The user-visible CareerVector app stays Svelte/TypeScript; the desktop build adds a native Tauri layer for capabilities that a hosted browser tab cannot provide.

The source-specific adapter code is intentionally outside this proposal. The desktop runtime only needs to execute bounded device tasks and return observations matching the device contract. Each submitted Observation anchors to one ad_id and contains a fields map, evidence refs, object refs, raw chunk refs, annotations, search chunks, embeddings, errors, and resource metadata. After submitObservation, those fields become evidence, chunks, Embedding records, and current ad cells at the same shared boundary used by hosted CareerVector and JobCache. The desktop app is a trigger, not a separate data shape.

Tauri is more than a bundler. It is the native application shell, permission system, Rust backend bridge, and packaging path. For our purposes, it packages the same Svelte client into a desktop app and exposes native commands/plugins behind a runtime interface.

Product contract

CareerVector should have two free access paths:

  • Web app: free, normal hosted browser experience, no scrape contribution.
  • Desktop app: free, richer native runtime, scrape contribution accepted as part of installing and using the desktop app.

This makes the desktop app a contribution channel, not a paid tier. This tradeoff must be plain in the workspace download flow: the user gets the native app while the app contributes bounded spare compute/network capacity to jobcache while it is running. The contribution is not a hidden optional checkbox inside the desktop product; the alternative is the web app.

The desktop app should still expose pause/status/limits because users need operational control over their machine. Those controls do not change the product contract; they prevent accidental overuse and make the contribution observable.

Workspace-bound distribution

The desktop app should be downloadable from every workspace and should arrive already bound to that workspace. A user should not have to remember a workspace URL, API host, tenant slug, or bootstrap token.

The important distinction is that the user experience should be workspace-specific, while the signed binary should stay as generic as possible. Rebuilding and re-signing a full Tauri app for every workspace download would be slow, expensive, and brittle. Prefer a stable signed app plus a workspace-specific bootstrap bundle.

Download flow:

  1. User clicks Download desktop app inside a workspace.
  2. Server creates a short-lived desktop enrollment record for that workspace.
  3. Download bundle includes or launches a signed bootstrap manifest with: workspace id, workspace display name, workspace URL, CareerVector API base URL, device broker endpoint, update channel, contribution policy, and one-time enrollment token.
  4. First launch consumes the token, stores the workspace binding locally, and registers the device as a desktop contributor for that workspace.
  5. Later launches go straight to the bound workspace, with an account/workspace switcher only if the user deliberately adds more workspaces.

Platform details can differ. Windows can use an installer wrapper or installer property. Linux can use an AppImage/deb plus adjacent manifest or first-run launcher. macOS should avoid mutating the signed app bundle after signing; use a signed generic app plus a workspace enrollment file, custom protocol handoff, or signed installer wrapper. The product rule remains the same: the download starts from the workspace and first launch already knows where it belongs.

Why not pure hosted web scraping?

A normal browser tab cannot scrape arbitrary third-party HTML with fetch(). Browser JavaScript is bound by the same-origin policy and CORS. If the target site does not return browser-readable CORS headers, the browser may still send some requests, but it will not expose the response body to JavaScript. no-cors does not solve this; it produces an opaque response whose body and headers are unavailable.

WebAssembly does not create a new network privilege level. Browser-hosted WASM uses the web platform security model, so it inherits the same origin/CORS limits as JavaScript. WASM is useful for parsing, extraction, scoring, compression, and other CPU work once content is available; it is not a browser CORS escape hatch.

What still works in TypeScript?

TypeScript remains the right coordination language for the app and product UI. The constraint is the runtime, not the language.

  • Hosted web TypeScript can call CareerVector APIs, CORS-enabled public APIs, RSS/Atom/sitemap endpoints that opt into browser reads, and user-provided imports.
  • Hosted web TypeScript can process, normalize, and validate content fetched by our server or by a native runtime.
  • Desktop TypeScript can initiate scrape tasks through a Tauri runtime adapter. The actual network call then goes through the Tauri HTTP plugin or a Rust command, not through browser window.fetch.
  • Rust should own the low-level native device runtime when we need streaming, request policy enforcement, local queues, resource limits, and stable packaging.

Runtime split

packages/
  app/                 # shared Svelte UI and app state
  domain/              # ads, jobs, extraction contracts, task schemas
  device-contract/     # task/observation schemas, provenance, throttling
  runtime-web/         # browser-safe APIs only
  runtime-tauri/       # Tauri commands/plugins behind same interface
src-tauri/
  commands/            # native fetch, queue, resource telemetry
  device/              # bounded device runtime; Adapters are external

The Svelte app imports a RuntimeCapabilities interface rather than importing Tauri directly across product code. The web runtime reports canNativeFetch: false; the desktop runtime reports canNativeFetch: true only after local permissions and server-issued task leases are available.

Edge Device Flow

  1. Desktop app starts after a workspace-bound install where contribution was accepted as part of the desktop access model.
  2. Client reports coarse capacity: app version, platform, idle budget, max concurrency, supported_task_classes, and allowed_task_classes.
  3. Broker leases small tasks with source id, URL, adapter version, freshness target, and expected Observation schema.
  4. Native device runtime fetches content with per-source rate limits and local timeout budgets.
  5. Device stores raw response metadata locally only long enough to extract and submit evidence.
  6. Client submits Observations with one URL x language ad_id, fields[field].cell, evidence refs, chunk refs, object refs, errors, resource metadata, adapter version, timing, and content hashes. Embedding leases submit Observations that carry Embedding records.
  7. submitObservation validates cheap invariants and the active lease, compares with current cells, updates global device trust, writes latest valid cells and attachments to Cockroach, and queues verification work for fresh conflicts. Large raw blobs may go to online S3-compatible storage by stable key as replaceable cold evidence.

Cockroach remains canonical. Desktop clients are untrusted contributors. Every submitted Observation needs provenance, schema validation, deduplication, and replayable evidence handling before it enters the shared boundary. Object storage is not canonical; it supports inspection and replay.

Server role

The server should be dumb but shrewd. It should know the shared ad state, available devices, pending work, source health, device trust, and conflict thresholds. It should not normally redo expensive work itself. The whole point of the desktop fleet is that fetch, extraction, embedding, geocoding, classification, and other heavy jobs are outsourced.

The server's business record is shared ad identity, field definitions, sparse current cells, device trust, task/lease history, and attachment keys in Cockroach. Online S3-compatible storage holds raw evidence blobs only when they are useful for replay or QA. The system must be able to keep serving accepted ad cells, role projections, and private workspace projections even if cold evidence is re-fetched or replaced.

The hard budget guardrail is zero paid central compute for jobcache ingestion and enrichment. The server is allowed to spend tiny control-plane work on leases, lease validation, conflict scoring, counters, and writes, but it must not become the place where scraping, embedding, graph derivation, LLM extraction, geocoding, or verification runs. If free-tier limits are tight, the system slows down or waits for client capacity instead of buying central compute.

Server-side work should stay cheap:

  • lease scheduling;
  • schema, size, hash, and version checks;
  • exact or tolerance-based comparison of independent client Observations;
  • canary task generation from already accepted observations;
  • device-trust updates;
  • quarantine/admit decisions;
  • Cockroach writes and optional S3-compatible evidence writes;
  • kill switches and backpressure.

If the server is unsure, it should not compute the answer. It should become more picky about who receives the task, assign duplicate work to another device, wait for more clients, or quarantine it. Work done beats perfect accuracy, as long as device trust is explicit and bad observations remain traceable and reversible.

Device trust

Each device should earn trust scoped by task class, source family, adapter/model version, and runtime capability. A device that is good at static HTML extraction does not automatically become trusted for embeddings, graph-edge candidates, or geocoding.

Trust updates come from outsourced verification, not central recomputation:

  • canary tasks where the accepted answer is already known;
  • redundant assignment to independent devices;
  • agreement with later consensus;
  • stable behavior across app versions and model packs;
  • resource-policy compliance, such as respecting battery, thermal, and network limits.

Low-trust devices still do useful work, but their outputs need more independent agreement before they can change current cells. High-trust devices get lower audit rates and more valuable task classes. Suspicious devices lose task classes or are revoked.

Embeddings fit this model if the model pack is pinned. The task lease should specify text hash, chunking version, tokenizer version, model id, model version, quantization, expected dimensions, and output normalization. The server can then compare vector hashes or quantized/tolerance-bounded vectors from independent clients without running the model itself.

Weaknesses

This architecture buys near-zero central compute by accepting slower and less predictable throughput. It works only if the product can tolerate eventual freshness and device-trust-weighted observations instead of guaranteed immediate coverage.

Main weak points:

  • Bootstrap problem: before there are enough desktop users, the shared ad set grows slowly unless we seed it manually or accept a very small initial scope.
  • Free-tier fragility: even a dumb server can hit database, storage, bandwidth, logging, observability, or build-minute limits if writes are noisy.
  • Client churn: laptops sleep, networks change, and apps close. Leases must be short and task batches small.
  • Verification lag: low-trust Observations may wait for another device instead of being accepted immediately.
  • Source skew: if most clients are in the same geography or network class, some sources may still see correlated traffic patterns.
  • Update pressure: adapter modules, model packs, and task schemas need careful versioning or old clients will keep producing unusable observations.
  • Poisoning: bad devices can still waste work and submit plausible junk. Scoped trust, canaries, duplicate assignment, quarantine, and rollback reduce the damage; they do not make bad input impossible.
  • User trust: contribution must never cause heat, battery drain, metered-network usage, or visible slowdown. If users feel the desktop app abuses their machine, the compute fleet disappears.

The design response is not to add server compute. It is to be picky with task assignment, degrade gracefully, and keep every expensive step outsourceable.

Product guardrails

  • Contribution is explicit and visible in the desktop app.
  • Web access remains available for users who do not want desktop contribution.
  • Workspace binding is local, visible, and revocable from the workspace.
  • No browsing history collection.
  • No credential, cookie, local browser profile, or session reuse.
  • No hidden background scraping after the app is closed.
  • Clear per-device quotas, pause controls, and source allow/deny configuration.
  • Server-side task leases prevent duplicate work and let us shut down bad source runs centrally.
  • Stable input keys prevent repeated extraction, embedding, enrichment, geocoding, and matching work for unchanged content.

Efficiency target

The desktop runtime is useful only if it reduces central compute and central egress pressure without creating noisy duplicate work. The design therefore prioritizes:

  • small leased batches instead of open-ended crawling;
  • conditional requests and source freshness windows;
  • adapter versioning so old work is replayable when extraction logic improves;
  • local prefiltering before upload;
  • content hashes before expensive enrichment;
  • central dedup before leasing vector, graph, and LLM work;
  • backpressure from Cockroach RU usage, queue depth, and source error rates.

Implementation references checked 2026-05-29

  • MDN CORS guide: browser fetch() and XMLHttpRequest follow same-origin/CORS rules unless the response includes the right headers.
  • MDN Fetch guide: no-cors responses are opaque and unreadable by JavaScript.
  • WebAssembly web embedding notes: browser-hosted WASM is no looser than JavaScript from a security point of view and depends on the same-origin model.
  • Tauri SvelteKit guide: Tauri packages a static/SPA-style SvelteKit frontend and exposes Tauri APIs in the webview.
  • Tauri HTTP plugin: JavaScript can call the plugin fetch API, backed by native Rust HTTP configuration and explicit URL permissions.
  • Tauri command guide: the frontend can invoke async Rust commands with typed arguments and return values.
Source: jobcache/wiki/content/architecture/desktop-edge-runtime.md