# Data Field Mapping — Collector Output vs Page JS Expectations

These are the most common post-W3C-audit failure points. Every time a page's JS is
rewritten, re-verify these six paths against the ACTUAL JSON output.

## Canonical Mapping

| JS reads | Collector provides | Type |
|----------|-------------------|------|
| `data.cron_jobs` | `cron_jobs` | Array of {name, schedule, lastRun, status, script} |
| `data.services` | `services` | Dict {service_name: status_string} |
| `data.s3_backups` | `s3_backups` | Dict {bucket_name: {status, last_upload, age_hours}} |
| `data.api_checks` | `api_checks` | Dict {check_name: status_string_or_emoji} |
| `data.overall.disk_used_pct` | `overall.disk_used_pct` | Integer (0-100) |
| `data.overall.memory_used_pct` | `overall.memory_used_pct` | Integer (0-100) |
| `data.cloudflare_zones` | `cloudflare_zones` | Array of {name, status} |
| `data.netcup_server` | `netcup_server` | Dict {id, template, ip} |
| `data.hetzner_servers` | `hetzner_servers` | Array of {name, type, status, ip, id} |
| `data.syncromsp` | `syncromsp` | Dict {status, customers, tickets, customer_list} |
| `job.lastRun` | `lastRun` | String (ISO 8601 with TZ offset) — NOT `last_run` |

## Common wrong expectations (DO NOT use)
- `scheduled_jobs` — use `cron_jobs`
- `api_health` — use `api_checks` (dict, not array)
- `disk_used_pct` at top level — it's inside `overall`
- `memory_used_pct` at top level — it's inside `overall`
- `job.last_run` (snake_case) — use `job.lastRun` (camelCase)
- `services` as flat array — it's a dict, iterate `Object.keys(services)`
- `s3_backups` as array — it's a dict {bucket_name: content}
- `api_checks` as array — it's a dict {check_name: status_string}

## Root cause
The W3C subagent writes new markup and JS from the task brief's schema description,
not from the actual JSON file on disk. Always read the live file before writing JS.

## Verification command
```bash
python3 -c "import json; d=json.load(open('/var/www/ops/data/ops-status.json')); cj=d['cron_jobs'][0]; print('lastRun:', cj.get('lastRun','MISSING')); print('last_run:', cj.get('last_run','check')); print('services type:', type(d['services']).__name__)"
```

## Post-audit checklist
After ANY page markup change, before declaring complete:
1. Run the verification command above
2. Check all JS field references match the actual JSON output
3. curl the page for HTTP 200
4. curl the data endpoint for valid JSON
5. Verify nav injection works (check for initNav in both the page and utils.js)
6. Verify files have 644 perms (Caddy returns 403 on 600 files)
