# How `update-system.mjs` Manages Version Checking, Apply, Dismiss, and Rollback in santifer/career-ops

> Discover how santifer/career-ops update-system.mjs safely handles version checking, apply, dismiss, and rollback commands to update your system without user data loss.

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

---

**The `update-system.mjs` script in santifer/career-ops provides four CLI commands—`check`, `apply`, `dismiss`, and `rollback`—that safely update the system layer without touching user data, using semantic version comparison, backup branches, and a hidden marker file for suppression.**

The `update-system.mjs` script serves as the safe auto-updater for the *career-ops* repository. It operates exclusively on the **system layer**—mode files, scripts, and templates—while strictly protecting any user-layer data. This article breaks down exactly how the script handles version checking, applying updates, dismissing notifications, and rolling back changes, based on the source implementation in `santifer/career-ops`.

---

## Version Checking in update-system.mjs

The `check` command determines whether a newer version exists upstream through a three-step process.

### Reading Local and Remote Versions

`localVersion()` reads the `VERSION` file at the repository root and normalizes its contents:

```javascript
function localVersion() {
  const vPath = join(ROOT, 'VERSION');
  return existsSync(vPath) ? parseVersionFile(readFileSync(vPath, 'utf-8')) : '0.0.0';
}

```

*(source: lines 53-63 of `update-system.mjs`)*

For the remote version, the script fetches the latest commit via `git fetch` and reads the upstream `VERSION` from `FETCH_HEAD` using the `RAW_VERSION_URL` constant.

### Semantic Version Comparison

`compareVersions(a, b)` implements standard semver logic:

```javascript
function compareVersions(a, b) {
  const pa = a.split('.').map(Number);
  const pb = b.split('.').map(Number);
  for (let i = 0; i < 3; i++) {
    if ((pa[i] || 0) < (pb[i] || 0)) return -1;
    if ((pa[i] || 0) > (pb[i] || 0)) return 1;
  }
  return 0;
}

```

*(source: lines 64-71 of `update-system.mjs`)*

If the remote version is greater, the script outputs `{"status": "update-available", ...}`; otherwise, it returns `{"status": "up-to-date"}`.

---

## Applying Updates Safely

The `apply` command implements a multi-layer safety protocol before modifying any files.

### Pre-Update Safety Checks

1. **Build user-layer path list** – `effectiveUserPaths()` combines the built-in `USER_PATHS` array with any additional entries from [`config/local-paths.txt`](https://github.com/santifer/career-ops/blob/main/config/local-paths.txt)
2. **Detect conflicts** – `userLayerViolations()` aborts if any `SYSTEM_PATHS` entry overlaps with user-layer paths
3. **Flag local modifications** – `locallyModifiedSystemFiles()` identifies system files the user has edited; these receive `.bak` preservation copies

### Executing the Update

The script performs a **raw checkout** (no merge) for each system path:

```bash
git checkout FETCH_HEAD -- <path>

```

After checkout, it stages changes, commits with a message including the backup branch name, and creates a timestamped backup branch via `updateBackupBranchName()`. The `.update-dismissed` marker is removed if present.

---

## Dismissing Update Checks

The `dismiss` command suppresses automatic update notifications until explicitly re-enabled:

```javascript
function dismiss() {
  writeFileSync(join(ROOT, '.update-dismissed'), new Date().toISOString());
  console.log('Update check dismissed. Run "node update-system.mjs check" or say "check for updates" to re-enable.');
}

```

*(source: lines 2308-2310 of `update-system.mjs`)*

When `.update-dismissed` exists, subsequent `check` commands return `{"status": "dismissed"}` without network activity.

---

## Rolling Back Updates

Rollback restores the pre-update state using backup branches created during `apply`.

### Backup Branch Naming Convention

`updateBackupBranchName()` generates recoverable branch names:

```javascript
function updateBackupBranchName(version, date = new Date()) {
  const stamp = date.toISOString()
    .replace(/[-:]/g, '')
    .replace(/\.\d{3}Z$/, 'Z');
  return `backup-pre-update-${version}-${stamp}`;
}

```

### Rollback Execution Process

1. `newestBackupBranch()` parses `git branch` output to find the most recent backup
2. The identified branch is checked out to restore original system files
3. The backup branch is deleted after successful restoration
4. Files introduced by the rolled-back update are removed from the working tree

---

## Practical Usage Examples

| Command | Purpose | Example Output |
|---------|---------|----------------|
| `node update-system.mjs check` | Query for available updates | `{"status": "update-available", "local": "1.8.0", "remote": "1.9.0"}` |
| `node update-system.mjs apply` | Apply the latest update safely | Success message with backup branch name |
| `node update-system.mjs apply --force` | Overwrite local system file edits | Same as above, skipping modification checks |
| `node update-system.mjs dismiss` | Suppress future update prompts | `Update check dismissed...` |
| `node update-system.mjs rollback` | Revert to pre-update state | Restoration confirmation |

---

## Key Files and Constants in update-system.mjs

| File/Constant | Role |
|-------------|------|
| `update-system.mjs` | Core auto-updater implementation with all four commands |
| `VERSION` | Local semantic version identifier |
| `.update-dismissed` | Hidden marker file disabling automatic checks |
| `SYSTEM_PATHS` | Whitelist of system-layer paths safe to overwrite |
| `USER_PATHS` | Protected user-layer paths that block updates on conflict |
| [`config/local-paths.txt`](https://github.com/santifer/career-ops/blob/main/config/local-paths.txt) | Optional extension for fork-specific protected paths |

---

## Summary

- **Version checking** in `update-system.mjs` compares local `VERSION` against remote using strict semver logic
- **Applying updates** validates against user-layer conflicts, preserves modified files, creates recoverable backups, and performs raw checkouts
- **Dismissing checks** writes a `.update-dismissed` marker that short-circuits future `check` commands
- **Rolling back** locates the newest `backup-pre-update-*` branch and restores that state cleanly

---

## Frequently Asked Questions

### How does update-system.mjs prevent overwriting my data?

The script builds a protection list via `effectiveUserPaths()` combining `USER_PATHS` and [`config/local-paths.txt`](https://github.com/santifer/career-ops/blob/main/config/local-paths.txt). `userLayerViolations()` aborts the update if any `SYSTEM_PATHS` entry conflicts with these protected paths. Additionally, `locallyModifiedSystemFiles()` creates `.bak` copies of system files you've edited before checkout.

### Can I force an update if I've modified system files?

Yes. Run `node update-system.mjs apply --force` to bypass the local modification check. Without `--force`, the script preserves your edits as `.bak` files and aborts.

### How long does a dismissed update stay suppressed?

Indefinitely. The `.update-dismissed` marker persists until you explicitly run `node update-system.mjs check` or remove the file manually. There is no automatic expiration.

### Where are rollback backups stored?

Backup branches remain in your local git repository following the pattern `backup-pre-update-{version}-{timestamp}`. These branches are local-only and deleted after successful rollback.