Product engine — shared mechanism, separate products

Aligned 2026-08-17. CareerVector and JobCache are two product domains. They reuse code where the mechanism is genuinely the same; they do not collapse their meaning, identity or data ownership into one application.

CareerVector serves one human applying for work. JobCache provides the eagle-eyed market view. JobCache itself contains an observer-facing App zone and a shared Core zone. “Core” is a concern and privacy boundary inside JobCache, not a third product or a required package name.

The reusable archetype

Both product edges use the same small set of mechanisms:

Mechanism CareerVector installation JobCache App installation
link capability workspace_id session_id
local private store CareerVector D1/Yjs JobCache App D1
Core pseudonym versioned person_key versioned observer_key
public automation REST plus MCP REST plus MCP
Core result handling immutable workspace grant/pointer immutable session grant/pointer
deletion local erase plus durable Core tombstone local erase plus durable Core tombstone

This equivalence is an implementation aid, not an identity equivalence. A CareerVector workspace ID is never a JobCache session ID. Neither crosses into JobCache Core. Shared code receives a neutral local-subject port; the product adapter supplies its own noun, store and participant profile.

What is actually shared

Home Responsibility
lib/corpus-shared/ strict cross-product privacy, participant, fact, delivery, search, snapshot and lifecycle contracts
lib/*-platform/ vendor-neutral HTTP/MCP/runtime mechanics once their ports are proven
jobcache/shared/ JobCache-owned Core logic: corpus, role/ad projections, extraction, search, recommendation and metrics
@cv/public-ui and UI kit packages one landing, product shell, HeaderBar/SubHeaderBar and link-identity actions consumed by both CareerVector and JobCache
Sporewright generic routing/decision substrate; it does not own product identity or the corpus seam

The public APIs remain product-specific. Sharing a serializer or store port does not justify a generic endpoint with optional product flags. If a behavior means something different to an applicant and a market observer, each product owns the operation and calls the shared mechanism underneath it.

Session and workspace capabilities

A JobCache session and a CareerVector workspace use the same authorization model: an unguessable URL-safe identifier is the access capability, with no account database. That is the reusable part.

The stored state differs:

  • CareerVector owns CV/CL documents, applications, notes, evaluations and applicant interactions.
  • JobCache App owns saved market views, dashboard state, observer interactions and locally granted result snapshots.
  • JobCache Core owns public ads, roles, evidence, embeddings and accepted purpose-scoped shared facts.

There is deliberately no session enumeration endpoint. API and MCP calls carry the explicit capability; servers do not infer a current session from cookies, environment variables or mutable server-side defaults.

Provider-neutral ports, concrete adapters

The reusable service layer depends on a storage port, not D1 or CrateDB types. Each installation then binds the appropriate adapter:

Data Runtime/store
CareerVector private workspace state Cloudflare Worker + D1/Yjs
JobCache App private session state Cloudflare Worker + its own D1
JobCache Core corpus JobCache service + CrateDB
Core operational leases/receipts an operational store beside the scoped internal API

The old jobcache_sessions table may remain in historical Crate schemas, but its runtime adapter has been removed. It mixed App-local identity with the Core store and is not a basis for new clients.

API first, MCP second, UI on the same boundary

The current build order is intentional:

  1. Define and test strict pure contracts in shared packages.
  2. Complete JobCache Core's authenticated search and participant lifecycle operations.
  3. Build jobcache/api/ as the session-private App boundary over D1. It owns the Core credential and never forwards session_id.
  4. Build jobcache/mcp/ as a thin peer over that API. It must not query CrateDB or reimplement policy in-process.
  5. Build jobcache/ui/ as another client of the same stable API. It reuses the shared shell/chrome and reaches App through a Cloudflare service binding; it does not get a private state path or a Core credential.

The provider-suspended Render bundle is no longer an application architecture: its UI, embedded MCP and Host router have been removed. The Core process keeps only explicit compatibility reads and protected Core boundaries.

Privacy class is selected by the operation

Every App-to-Core operation declares one of two classes:

  • An individualized observer operation carries a versioned observer_key in a strict pseudonymous envelope and has retention, erasure and rotation rules.
  • A market aggregate carries no participant key and crosses only after the App edge has actually formed a permitted cohort and met its configured threshold.

A single session cannot declare itself an anonymous cohort. Storing an aggregation request or threshold policy locally is not the same as emitting an anonymous aggregate. Until a reducer proves membership and threshold, nothing crosses under the aggregate contract.

Reuse rules

  • Extract pure algorithms and explicit ports; do not copy route handlers between products.
  • Keep D1, CrateDB and provider bindings behind adapters.
  • Keep JobCache Core code under JobCache naming; internals is diagram prose, not a public namespace.
  • MCP mirrors API capabilities and policy. It never bypasses the App API to reach Core storage.
  • The UI calls the same API as MCP. No UI-only mutations or implicit session state.
  • Do not import CareerVector-specific realtime, mutation, LLM or workspace intelligence machinery merely because the link-capability shape is shared.
  • Do not expose local identifiers in logs, Core request bodies, result metadata or lifecycle receipts.

Current status

CareerVector is the first deployed proving installation of the seam mechanics. JobCache Core's corpus, strict role search and participant tombstone lifecycle exist. jobcache/api/ now implements the App's opaque sessions, D1 adapter, saved views, immutable snapshot grants, explicit reductions, delivery and session-erasure outbox. jobcache/mcp/ is a thin API peer with no Core secret.

The App UI, API and MCP Workers are live. jobcache/ui/ implements the Cloudflare/Svelte shell as a thin App client: CreateLanding, AppShell, both header rows, responsive tab pills, theme and common link actions are shared code, while JobCache supplies session wording and product tabs. Remaining UI work is product content, beginning with Search jobs and its Swiss map; it is not another backend architecture.

Source: wiki/content/architecture/PRODUCT-ENGINE.md