# Atlas Client-Stage Cron Schedules

5 standalone cron jobs that run alongside Atlas. They write directly to the Atlas Postgres DB (via `_pg.mjs`) and log to `stage_history` with `actor_id='system:cron:<job>'`, `channel='cron'`.

## What each job does

| Job | Schedule | What it does |
|-----|----------|--------------|
| `cron_stage_autoroll_eom_open.mjs` | Daily 06:00 CT (script self-guards to last business day) | Flips all `weekly` clients to `eom_close` on the last business day, respecting `close_cadence` (monthly every month, quarterly Mar/Jun/Sep/Dec, annual Dec only). |
| `cron_flag_autoclear_advisory_due.mjs` | Daily 06:00 CT (script self-guards to day 16) | Clears `advisory_due` flag from any client where it's still set on day 16 of the month. |
| `cron_stage_stuck_detector.mjs` | Hourly | Sets `stuck` flag on any client exceeding per-stage SLA (10d for eom_close, 4d for eom_review, 30d onboarding/paused_client, 14d paused_internal, plus 6d on advisory_due flag age). |
| `cron_flag_sales_tax_due.mjs` | Daily 06:30 CT | Sets/clears `sales_tax_due` based on per-client `sales_tax_schedule` (monthly/quarterly/annual default windows). |
| `cron_flag_seasonal.mjs` | Daily 06:00 CT | Sets/clears `1099_prep` (Jan 1 – Feb 15) and `year_end` (Dec 1 – Jan 15) flags. |

All jobs are **idempotent** — running them multiple times in the same window produces zero side effects.

## Manual dry-run

Each script supports `--dry-run` (prints the changes it WOULD make, rolls back the transaction). Useful for verifying SLA tuning.

```sh
cd C:\PKA\Atlas\app
node scripts/cron_stage_stuck_detector.mjs --dry-run
node scripts/cron_stage_autoroll_eom_open.mjs --dry-run --force   # --force overrides date guard
```

## Installation on Minas (the headless server)

Three files to copy to the Atlas host:

1. `run-cron.bat` — wrapper that sets the working directory before invoking node
2. `register-all.ps1` — PowerShell script that creates the 5 Task Scheduler entries
3. (Optional) `unregister-all.ps1` — removes them

### Install steps

```powershell
# Run from elevated PowerShell on Minas:
cd C:\PKA\Team Inbox\Forge\cron-schedules
.\register-all.ps1
```

The script creates 5 entries under the `Atlas\` folder in Task Scheduler. They run under the logged-on user (no SYSTEM account needed) because the DB connection uses `.env.local` which lives in the user-context filesystem.

### Verifying they're registered

```powershell
schtasks /Query /FO TABLE | findstr Atlas
```

Should show 5 entries (plus any existing Atlas tasks like the start-atlas wrapper).

### Unregistering

```powershell
.\unregister-all.ps1
```

## Per-task XML files

`xml/` subdirectory contains one XML per task for GUI-import users. Generated by exporting from `schtasks /Query /XML` after `register-all.ps1` runs once; they're checked in here as the source-of-truth definition. Re-running `register-all.ps1` is the canonical install path.

## Verifying after install

After tasks are running, the audit trail will show cron-driven changes:

```sh
psql "$DATABASE_URL" -c "
  SELECT actor_id, COUNT(*)
  FROM stage_history
  WHERE actor_id LIKE 'system:cron:%'
  GROUP BY actor_id
  ORDER BY COUNT(*) DESC;
"
```

If 24 hours pass and `system:cron:stage_stuck_detector` shows zero entries, the hourly cron isn't firing — check Task Scheduler history for the Atlas\Stuck Detector entry.
