The Shared Seam Packages
Status (2026-08-17): @cv/corpus-shared is integrated by CareerVector, JobCache Core
and the new JobCache App. Pure participant/privacy/search/snapshot/fact/delivery/lifecycle
contracts are shared; each product still owns its routes, authorization and local store.
CareerVector remains the first deployed installation. JobCache App is a complete second
code installation, but its Worker and D1 are not deployed.
System Architecture establishes a mirror between CareerVector's workspace-private edge and JobCache App's session-private edge. The public products differ; the seam mechanism must not.
Naming and ownership
There are two distinct kinds of “shared” code:
| Home | Owns |
|---|---|
lib/corpus-shared/ (@cv/corpus-shared) |
pure cross-product contracts and algorithms: participant profiles, privacy envelopes, canonical hashing, fact and delivery types, request keys and immutable result identity |
jobcache/shared/ and its existing packages |
JobCache Core domain: corpus persistence, ads/roles, extraction, search, recommendation and market metrics |
@cv/* is the repository's established package namespace and does not mean that JobCache
Core belongs to CareerVector. JobCache App and JobCache Core remain JobCache. “Core” is a
concern/privacy zone, not a third deployable required by this architecture.
A future seam route/store installer belongs in the existing lib/*-platform family once
the CareerVector implementation exposes stable ports. Until then, route code remains in its
host rather than pretending an incomplete abstraction is reusable.
What is shared
| Row | Shared? | Boundary |
|---|---|---|
| product perspectives | no | RADAR and JobCache browse/dashboard are different products |
| public API | no | workspace authorization and session authorization remain product-owned |
| result/fact/privacy contracts | yes | pure @cv/corpus-shared code |
| local store schema and reduction mechanics | yes | neutral local-subject model behind a store port |
| fast and slow collectors | yes | identical reliability and privacy protocol |
| seam-facing claim/ack/deliver routes | yes | reusable installer after store and auth ports are proven |
| JobCache Core search/recommendation/metrics | no | JobCache-owned Core implementation |
The shareable unit stops at each product's public API and at JobCache Core's domain logic. It does not absorb UI copy, workspace/session authorization, CrateDB queries or ranking.
One coherent participant profile
The host selects one participant kind:
| Installation | Kind | Local identity | Pseudonym |
|---|---|---|---|
| CareerVector | person |
workspace_id |
person_key |
| JobCache App | observer |
session_id |
observer_key |
Prefix, secret binding, writer/reducer identity and graph kind are derived from that profile, not independently configurable strings. This prevents impossible combinations such as an observer signed with the person secret. The host separately supplies its store, auth and application adapters. Participant references are versioned HMAC values minted by the shared algorithm; only the host retains the inverse map to its local identity.
Product-specific behavior is not represented by boolean flags. If a difference belongs to RADAR or a JobCache dashboard, it stays above the shared boundary.
Privacy contracts
Every outbound envelope is one of:
- pseudonymous individual — contains a versioned
person_keyorobserver_keyand an allowlisted payload; - anonymous aggregate — contains no participant key and includes the satisfied cohort threshold and reducer version.
Unknown classes, unknown fields and local product IDs fail validation. Pseudonymous and anonymous payloads are different schemas, not optional fields in one permissive object.
Result identity
The shared layer distinguishes:
| Identifier | Meaning |
|---|---|
| request key | canonical equivalence key for deduplicating the same requested work |
| result ID | immutable identity of canonical ordered result bytes and all result-changing metadata |
| local pointer | the one current snapshot selected for a workspace/session |
The same result ID can never name different bytes. limit, corpus revision,
retrieval/ranking version and other result-changing context participate in identity. A
public route still authorizes through the local pointer; a deterministic key is not public
authorization.
Pull and delivery protocol
The shared contract requires:
- a typed participant reference minted only by the product edge: new products use
person:vN:<sha256>, while CareerVector's already-persistedpk_<40-hex>identity is represented explicitly as compatibility epoch 0 until Core has a tested alias/backfill; - a stable SHA-256-derived
factIdover the complete canonical fact material, including purpose, privacy envelope, occurrence and payload; - a
leaseTokenbound to the authenticated claimant; - exact ack partitions:
acceptedFactIdsplus itemizedrejected[{ factId, reason }]; - durable quarantine for rejected facts and durable ack receipts;
- a byte-bounded claim, not only an item count;
- a composite
(timestamp, durable source sequence)cursor for every independently mutable event stream; - distinct shown and picked streams, so a positive label arriving after a shown acknowledgement is still claimable;
- a required stable
batchIdand durable delivery receipt; - strict result-row and allowlisted semantic-metadata validation on ingress;
- an explicit privacy context on delivery: the workspace-specific route accepts a pseudonymous person reference and refuses anonymous aggregates or observer references;
- endpoint-owned allowlists for fact types, purposes, participant kinds, aggregate dimensions and reducer versions; a producer cannot broaden its domain by choosing a weaker envelope;
- a named worker credential plus an explicit operation allowlist for
claim,ack,deliver,erase,reduceandcoverage; the general JobCache control token has no authority on the all-workspace seam; - content-derived delivery result IDs when Core does not provide one;
- immutable result rows and an atomic result-plus-pointer commit.
Cursor advancement follows durable acceptance, never proposal. A retry cannot create a second logical fact or delivery.
Current extraction map
| Current code | Destination/state |
|---|---|
lib/server/src/corpus/contract.ts |
pure types/algorithms move to @cv/corpus-shared; product integration stays |
lib/server/src/corpus/seam.ts |
verified JobCache Core role-search client plus CareerVector integration; legacy read doors remain interim and direct participant writes fail closed |
lib/server/src/corpus/lanes.ts |
neutral reduction/collector logic is an extraction candidate; CareerVector policy stays |
lib/server/src/corpus/scope-all.ts |
CareerVector store adapter; not cross-product API |
api/src/routers/internal-corpus.ts |
proving claim/ack/delivery implementation with authenticated leases, receipts, composite cursors and immutable grants; later becomes an installer over explicit auth/store ports |
migrations/corpus-seam.sql |
CareerVector installation, including composite cursors and the participant-erasure outbox/receipt tables |
api/src/routers/radar*.ts, ui/.../JobRadar.svelte |
CareerVector product code; stays |
jobcache/shared/src/corpus-lifecycle.ts |
Core participant tombstone, fail-closed writers, retryable cleanup and deterministic receipt implementation |
jobcache/api/ |
second edge installation: observer profile, opaque session/D1 adapter, immutable local grants, reductions, delivery and erasure outbox |
jobcache/mcp/ |
thin JobCache perspective over the App API; deliberately owns no Core secret |
| JobCache Core corpus/search code | stays under JobCache shared/domain packages and the current jobcache/interface Core facade |
The two edge installations intentionally do not share route handlers yet. The shared unit is the strict pure contract and content-identity machinery; D1 stores and public authorization remain product-owned. Extract a generic installer only when the now-concrete ports remain identical under further use, rather than erasing real product differences pre-emptively.
Separate from Sporewright and the LLM system
Sporewright owns generic addressed routing, uncertainty, curiosity and feedback mathematics. CareerVector's LLM routing is a separate CareerVector application. Neither owns corpus privacy envelopes, D1 seam schemas, collectors or JobCache Core persistence.