How update-system.mjs Enforces the Data Contract Boundary in Career-Ops

The update-system.mjs script enforces the data contract boundary by maintaining strict separation between system-layer files (which it can update) and user-layer files (which it protects), using static path declarations, runtime validation, and automatic violation detection to abort any update that would overwrite user data.

The Career-Ops repository implements a strict two-layer architecture to separate project infrastructure from personal content. The update-system.mjs auto-updater serves as the gatekeeper for this boundary, ensuring that automatic upgrades modify only the system layer while leaving every user-authored CV, profile, and report untouched. This mechanism is formally defined in DATA_CONTRACT.md and implemented through a series of defensive checks in the updater source code.

Static Declarations of System and User Layers

The boundary enforcement relies on two exhaustive static arrays declared in update-system.mjs.

The SYSTEM_PATHS array (lines 63‑73) contains an exhaustive list of paths belonging to the system layer—scripts, mode definitions, dashboards, and templates that ship with the project. These files are explicitly allowed to be overwritten during automatic updates.

Conversely, the USER_PATHS export (lines 104‑112) defines the user layer data contract. This hard-coded list protects files like cv.md, config/profile.yml, modes/_profile.md, and the reports/ directory from any automatic modification. These paths represent personal content that must survive system upgrades untouched.

Dynamic Path Resolution and Local Overrides

To accommodate fork-level customization without modifying source code, the script supports extending the user layer through an optional configuration file.

The localUserPaths() helper (lines 75‑84) parses config/local-paths.txt and validates each entry, rejecting absolute paths, parent directory references (..), and backslashes. This validation prevents path traversal attacks while allowing legitimate extensions to the protected user list.

The effectiveUserPaths() function (lines 133‑143) merges the built-in USER_PATHS export with any locally declared paths from localUserPaths(), producing the definitive runtime list of files protected by the data contract.

Violation Detection and Update Abort Logic

Before applying any changes, the updater validates operations against the effective user list. The userLayerViolations() function (lines 64‑73) iterates through every file modified in the upstream checkout. If a changed file exists within the effective user list and is not explicitly allowed by the update's updatePaths parameter, the function flags it as a contract violation.

During the apply() workflow, the script invokes userLayerViolations() and immediately aborts if violations are detected, outputting a JSON status describing the conflict. This ensures that automatic updates never proceed when user-layer files would be affected.

System Integrity and Backup Safeguards

Beyond protecting user files, the script implements safeguards for system file integrity and preservation of local modifications to system files.

The missingFromTargetManifest() function (lines 124‑136) verifies that every path listed in SYSTEM_PATHS actually materializes on disk after the checkout. This prevents silent partial updates that could leave the system in an inconsistent state.

For cases where users have legitimately edited system-layer files, the locallyModifiedSystemFiles() function (lines 88‑102) identifies system files that differ from upstream. Rather than silently overwriting these edits, the script backs up modified files with a .bak extension before replacement, satisfying the "no data loss" guarantee even for system-layer content.

Programmatic and CLI Usage

Developers can invoke the boundary checks programmatically or via command line interface.

// Example: checking an upcoming update for user‑layer violations
import { gitStatusEntries, effectiveUserPaths, userLayerViolations } from './update-system.mjs';

const changed = gitStatusEntries();                   // ← all files the update would modify
const updatePaths = SYSTEM_PATHS;                     // ← allowed paths for this run
const userPaths = effectiveUserPaths();               // ← full user‑layer list
const violations = userLayerViolations(changed, updatePaths, userPaths);

if (violations.length) {
  console.error('Update aborted – user‑layer files would be overwritten:');
  console.error(violations);
  process.exit(1);
}

# CLI usage – the script itself performs the same checks internally

node update-system.mjs check   # prints JSON with status/offline/etc.

node update-system.mjs apply   # aborts if any user‑layer violation is detected

Summary

  • The data contract separates SYSTEM_PATHS (updatable infrastructure) from USER_PATHS (protected user content) via static declarations in update-system.mjs.
  • localUserPaths() and effectiveUserPaths() allow fork-specific extensions while validating against path traversal vulnerabilities.
  • userLayerViolations() detects potential overwrites before they occur, triggering immediate aborts in the apply() workflow if user files are at risk.
  • missingFromTargetManifest() ensures complete system updates without silent file omissions.
  • locallyModifiedSystemFiles() backs up user edits to system files with .bak extensions before overwriting, preventing data loss.

Frequently Asked Questions

What happens if I add invalid paths to config/local-paths.txt?

The localUserPaths() function validates each entry and rejects absolute paths, parent directory references (..), and backslashes. Invalid entries are filtered out before being merged into the effective user list, protecting the repository against path traversal attacks while allowing legitimate extensions to the data contract.

Does the updater ever overwrite user-layer files?

No. The userLayerViolations() function explicitly checks every changed file against the effective user list built from USER_PATHS and local overrides. If any user-layer file would be modified, the apply() operation aborts immediately and reports the violation via JSON status, ensuring user data remains untouched without explicit consent.

How does the script handle my edits to system files?

The locallyModifiedSystemFiles() function (lines 88‑102) identifies system-layer files that differ from the upstream version. Before overwriting these files during an update, the script automatically creates .bak copies, preserving your local modifications while allowing the system upgrade to proceed.

Where is the data contract formally documented?

The two-layer contract is formally defined in DATA_CONTRACT.md at the repository root, while the enforcement implementation resides in update-system.mjs. Specific boundary logic is concentrated in functions like effectiveUserPaths() (lines 133‑143) and userLayerViolations() (lines 64‑73) according to the Career-Ops source code.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →