Privacy posture and data minimization

Status: normative engineering policy with both product-edge lifecycle implementations present in code. Production activation still requires the additive D1/CrateDB migrations, scoped credentials and JobCache App Worker deployment.

CareerVector and JobCache have no product account system. CareerVector uses an unguessable workspace link as identity and authorization. JobCache App uses request/session-local identity for saved observer state. Admin perspectives are separate authenticated operator tools; they do not turn product workspaces or sessions into accounts.

The objective is not to claim that identifiers evade data-protection law. Stable pseudonyms, workspace content and individualized behavior are personal data. The objective is to minimize collection, make every crossing purpose-specific and keep deletion possible.

Two products, three privacy zones

Zone May know Must not receive
CareerVector workspace_id, private applicant documents and interactions JobCache session_id
JobCache App session_id, private saved views and observer interactions CareerVector workspace_id
JobCache Core public corpus facts, approved pseudonymous facts and approved anonymous aggregates either local ID or inverse pseudonym map

JobCache Core belongs to JobCache while owning neither product edge's local identity. Each edge holds its own versioned pseudonym map and enforces its seam before serialization.

Data classes

Class Examples Crossing rule
product-local private workspace/session IDs, CV/CL, notes, dashboard state, raw interactions remains in the relevant edge unless a narrower processor contract explicitly requires an input
pseudonymous individual person_key, observer_key, reduced preference state, individualized graph facts allowed only on a named individual-purpose contract with retention, erasure and key versioning
anonymous aggregate thresholded cohort counts or distributions carries no participant key and crosses only after edge-side aggregation and thresholding
public corpus ads, public evidence, sources and role projections JobCache Core may retain and serve it subject to provenance and source terms

The API, fact type and purpose select the class. Code must never infer “anonymous” merely because an ID is absent, hashed or difficult to read. Unknown classification fails closed.

Individual and aggregate uses coexist

Both privacy classes are intentional:

  • Recommendation needs continuity for one person, so CareerVector may emit a versioned person_key with a tightly reduced, allowlisted artifact.
  • A genuinely individualized observer feature may use observer_key.
  • Market metrics should normally use anonymous thresholded cohorts with no participant key.
  • Person and observer remain distinct graph kinds. Observer browsing or dashboard refreshes never become applicant demand.

Anonymous cohort policy is configuration, not a silent default. Until minimum cohort size and allowed dimensions are configured, aggregate export is disabled.

Reduction and external processing

A reducer/exporter is named and versioned code. It declares:

  • input data class and purpose;
  • exact output schema and forbidden fields;
  • output privacy class;
  • consent or other product condition where required;
  • threshold and permitted dimensions for anonymous output;
  • retention and erasure behavior.

If raw text must be sent to an embedding or language-model processor, that is an explicit processor contract with its own data classification and retention terms. The derived vector does not make the raw input anonymous, and an individual vector remains pseudonymous when linked to a stable participant key.

Pseudonym lifecycle

Pseudonym keys are product-specific and versioned. They are never shared between CareerVector and JobCache App. Rotation requires dual-read/rekey handling or an explicit cutover; replacing a secret without migration is forbidden because it silently splits one participant into two histories.

CareerVector therefore keeps its deployed pk_<40-hex> bytes as shared-contract epoch 0. New person:vN:<sha256> identities are the target for a new installation, not an in-place rename. CareerVector moves to them only after JobCache Core can resolve the old and new keys as aliases and a backfill proves that existing facts remain one history.

Deleting either a CareerVector workspace or a JobCache App session atomically records a content-addressed participant-erasure command before removing its local inverse map and private rows. JobCache Core publishes a monotonic tombstone before cleanup, re-runs cleanup on replay, rejects late writes, and returns a deterministic receipt that contains no pseudonym. Reducers read active views that join against tombstones, so a crash can delay physical cleanup without making residue eligible again. The product edge removes the pending pseudonymous command only after validating that exact receipt; completed JobCache App outboxes retain neither the participant key nor a session identifier.

The protocols are implemented but intentionally inactive in production until their scoped credentials and additive migrations are deployed. Already-produced anonymous aggregates may remain only when they cannot be related back to that participant. Product-local result snapshots live until their workspace/session is erased. Operational stores retain only the minimum retry or receipt material required by their protocol.

Collection and logging

  • Do not add names, email addresses, phone numbers, device fingerprints or marketing trackers without a product requirement and a documented data contract.
  • Do not place local IDs, private text, pseudonym inverse maps, provider keys or raw fact payloads in logs, errors, metrics or routing receipts.
  • Authenticate internal APIs with scoped worker identities. Authentication makes an API internal-facing; it does not permit broader data.
  • Validate inbound deliveries against an allowlist before storing or returning them through a public product API.
  • Keep public result access scoped to the workspace/session pointer; deterministic query material must not become a guessable public result namespace.

Required tests

Privacy behavior is a release gate:

  • serializers reject workspace_id and session_id at both seams;
  • anonymous envelopes reject every participant key and fail below the configured threshold;
  • person and observer facts cannot be consumed by the wrong model;
  • log/error snapshots contain no forbidden fields;
  • key rotation preserves or deliberately tombstones history;
  • workspace/session deletion removes the inverse map and produces one idempotent erasure;
  • public result reads cannot enumerate another workspace/session's query or snapshot;
  • unknown fields and unknown privacy classes fail closed.

Data-subject operations

Product identity is the workspace/session capability, not an account row. Export and delete therefore operate on the state reachable through that capability and its pseudonymous downstream facts. Operator-assisted requests must verify control without creating a shadow identity database.

This page is engineering policy, not legal advice. Public privacy notices, retention values, processor agreements and paid-customer terms must describe the deployed system and receive appropriate legal review.

Source: wiki/content/architecture/PRIVACY-POSTURE.md