# CareerOps Data Contract Architecture: User Layer vs System Layer Explained

> Understand the CareerOps Data Contract Architecture. Explore the User Layer for personal data and the System Layer for upgradeable engine files in the santifer/career-ops repository. Learn how this separation ensures data integ...

- Repository: [Santiago Fernández de Valderrama/career-ops](https://github.com/santifer/career-ops)
- Tags: architecture
- Published: 2026-08-22

---

**The CareerOps Data Contract Architecture separates every file in the `santifer/career-ops` repository into two logical layers—the User Layer for personal data that must never be auto-updated, and the System Layer for core engine files that can be safely replaced during upgrades.**

The CareerOps Data Contract Architecture is the single source of truth that governs how the project maintains, updates, and customizes your career management system. This architectural pattern ensures that your personal CV, job tracker, and negotiation scripts remain intact while the underlying engine evolves with new features and bug fixes.

## Understanding the Two-Layer Architecture

The architecture bifurcates the repository into distinct zones with opposing update policies. This separation is documented in [[`DATA_CONTRACT.md`](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md)](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md) and enforced programmatically by scripts like `update-system.mjs`.

### User Layer: Protected Personal Data

The **User Layer** contains all files that store *personal* data, customizations, and user-generated output. According to the contract, the updater **must never** modify, edit, or delete these files during any release cycle.

**Core characteristics:**
- **Update policy:** Immutable during auto-updates (read-only for the updater)
- **Contents:** CV content, profile configurations, custom archetypes, negotiation scripts, application tracker data
- **Critical files:**
  - [`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md) — your CV in markdown format
  - [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) — personal identity, target roles, and compensation targets
  - [`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md) — custom archetypes and negotiation narratives
  - [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) — the canonical application tracker (source of truth)
  - [`portals.yml`](https://github.com/santifer/career-ops/blob/main/portals.yml) — maintained list of companies and search filters

### System Layer: Replaceable Core Engine

The **System Layer** implements the **engine** of CareerOps: scripts, templates, mode definitions, and supporting infrastructure. These files are designed to be ephemeral and disposable across releases.

**Core characteristics:**
- **Update policy:** Safe to **replace** on each release
- **Contents:** Core logic, scoring algorithms, UI components, PDF templates, documentation
- **Critical files:**
  - [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) — core scoring rules and shared utilities
  - [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) — evaluation mode instructions
  - `*.mjs` scripts — all utility scripts including `scan.mjs` and `set-status.mjs`
  - `templates/*` — HTML/CSS templates for CV PDFs and cover letters
  - `docs/*` — release documentation

## How the Data Contract Enforces File Protection

The contract operates on a simple binary rule enforced by the update mechanism. If a file appears in the User Layer, the updater may **read** it but must **not modify or delete** it. Conversely, System Layer files may be safely **replaced** with the newest upstream version.

The enforcement script `update-system.mjs` implements this logic by checking file paths against the contract definitions. The system also maintains **refused declarations** to prevent accidental freezing of system files—blocking absolute paths, paths already belonging to the system layer, and [`config/local-paths.txt`](https://github.com/santifer/career-ops/blob/main/config/local-paths.txt) itself from being incorrectly categorized as user data.

## Why CareerOps Uses a Two-Layer Design

Separating the repository into User and System layers serves three critical architectural purposes:

1. **Safety for personal data** — Your CV, job-tracker history, and negotiation narratives persist indefinitely without risk of corruption during library upgrades.
2. **Predictable upgrades** — The core engine can evolve with new modes, bug-fixes, and UI improvements without breaking your customizations or losing your application history.
3. **Clear boundaries for contributors** — New contributors can immediately identify which files are off-limits for automated changes versus which files they may modify for a release.

## Implementing the Contract in Practice

When extending CareerOps with custom modes or scripts, you must copy templates from the System Layer into the User Layer before modifying them. This pattern ensures the original template can receive upstream updates while your customized version remains protected.

**Copy a system template to the user layer (safe operation):**

```javascript
import { execSync } from 'child_process';

// Copy template from system layer to user layer
execSync('cp modes/_profile.template.md modes/_profile.md');

```

**Append custom content to a protected user file:**

```javascript
import fs from 'fs';

// Append to user-layer file - updater will never overwrite this
fs.appendFileSync('modes/_profile.md', '\n## New Archetype\n- Senior Data Engineer\n');

```

**Update script skipping user-layer files using `fast-glob`:**

```javascript
import glob from 'fast-glob';

// Select only system files, explicitly excluding user data directories
const systemFiles = await glob([
  '**/*.mjs', 
  'templates/**', 
  '!data/**', 
  '!modes/_profile.md'
]);

systemFiles.forEach(path => {
  // Safe to replace these files with upstream versions
  console.log(`Updating system file: ${path}`);
});

```

These examples demonstrate the golden rule: copy system templates **once** into the user layer, then treat them as immutable user-owned files that the updater cannot touch.

## Key Files by Layer

The contract categorizes specific paths to eliminate ambiguity during updates:

**User Layer (Never Auto-Updated):**
- [`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md) — Personal CV content
- [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) — Identity and targeting configuration
- [`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md) — Custom archetypes and scripts
- [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) — Application tracking data
- [`portals.yml`](https://github.com/santifer/career-ops/blob/main/portals.yml) — Company watchlists and filters

**System Layer (Auto-Updatable):**
- [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) — Shared evaluation logic
- [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) — System evaluation modes
- `scan.mjs` — Zero-token portal scanner implementation
- [`templates/cv-template.html`](https://github.com/santifer/career-ops/blob/main/templates/cv-template.html) — ATS-optimized PDF template
- [`DATA_CONTRACT.md`](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md) — The contract definition itself

## Summary

- The **CareerOps Data Contract Architecture** bifurcates the repository into User and System layers to protect personal data during automated updates.
- **User Layer** files ([`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md), [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml), [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md), etc.) are read-only for the updater and preserve your customizations indefinitely.
- **System Layer** files (`*.mjs`, `templates/*`, [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md), etc.) can be safely overwritten with each release to deliver new features.
- The contract is defined in [`DATA_CONTRACT.md`](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md) and enforced by `update-system.mjs` using path-based rules and refused declarations.
- Always copy system templates to the user layer before customizing them to ensure you benefit from upstream template improvements while keeping your modifications safe.

## Frequently Asked Questions

### What happens if I accidentally modify a System Layer file?

If you edit a System Layer file directly, your changes will be lost during the next update when `update-system.mjs` replaces the file with the upstream version. To preserve customizations, copy the file to the appropriate User Layer location (such as [`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md) for custom modes) and modify the copy instead.

### How does the update script distinguish between User and System Layers?

The `update-system.mjs` script references the canonical definitions in [`DATA_CONTRACT.md`](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md) and uses glob patterns (via `fast-glob`) to classify files. It explicitly excludes User Layer paths like `data/**` and [`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md) from the replacement list, ensuring these files are never overwritten even if they share filenames with system templates.

### Can I move a file from the System Layer to the User Layer?

Yes, you can copy any System Layer template into the User Layer to customize it. However, you cannot "move" the original system file—the System Layer must remain intact for the engine to function. Create your custom version in a User Layer location (e.g., copy [`_profile.template.md`](https://github.com/santifer/career-ops/blob/main/_profile.template.md) to [`_profile.md`](https://github.com/santifer/career-ops/blob/main/_profile.md)) and the updater will automatically protect your new file while continuing to update the original template.

### Where is the Data Contract formally documented?

The complete specification lives in [[`DATA_CONTRACT.md`](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md)](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md) at the repository root. This document serves as the single source of truth for the update policy, enumerating which paths belong to each layer and defining refused declarations that prevent accidental misclassification of system files as user data.