# Changelog of Website Change Intelligence Monitor (`woolly_kingcrab/website-change-intelligence-monitor`) Actor

- **URL**: https://apify.com/woolly_kingcrab/website-change-intelligence-monitor/changelog.md
- **Full Actor documentation**: https://apify.com/woolly_kingcrab/website-change-intelligence-monitor.md

## Changelog

### 0.2 — commercial upgrade (local only, not deployed)

#### Reliability

- **Per-URL failure isolation.** One bad URL no longer aborts the run. Each URL returns `SUCCESS` or a typed `FAILED` row with `changed_field` set to the reason: `invalid_url`, `blocked_url`, `dns_failed`, `timeout`, `http_error`, `content_too_large`, `parse_failed`, `internal_error`.
- Typed error hierarchy in `src/errors.py`; `fetcher.py` and `security.py` raise the specific type.
- Duplicate URLs (after normalisation) de-duplicated within a run.

#### Security (preserved + hardened)

- All existing SSRF protection kept: localhost, loopback, RFC-1918, link-local (incl. `169.254.169.254`), reserved, multicast, `*.local`, non-http(s) schemes, per-hop redirect re-validation, DNS-rebinding guard.
- URL normalisation is now deterministic: lowercased host, default ports dropped, fragment dropped, known tracking params (`utm_*`, `gclid`, `fbclid`, …) stripped — reduces false-positive link changes.

#### Change detection

- Flat output — one row per change, columns:
  `url, normalized_url, monitor_id, change_type, changed_field, previous_value, current_value, added_text, removed_text, previous_hash, current_hash, http_status, detected_at, first_seen_at, last_seen_at`.
- Change types limited to the provable set: `BASELINE`, `UNCHANGED`, `CONTENT_CHANGED`, `ADDED_TEXT`, `REMOVED_TEXT`, `PRICE_CHANGED`.
- `PRICE_CHANGED` uses a deterministic currency-token regex and only fires when both the previous and current page contain detectable prices.
- `first_seen_at` / `last_seen_at` tracked per page.
- `keywords` filter: report a page's changes only when the changed text/title matches.
- `outputMode`: `CHANGES_ONLY` (default) / `ALL_CHECKS`.

#### State

- State schema v2 with automatic migration from v1. A v1 record that cannot be migrated safely is dropped, so the next run re-baselines it (no fabricated change event).
- KVS name unchanged (`website-change-monitor-{monitorId}`) so existing saved monitors carry over.

#### Monetization

- `src/billing.py`: `page-checked` charged **only after** fetch + parse + compare succeed. Never for invalid/blocked/timeout/404/oversized/parse/internal failures.
- Per-run billing guard prevents double-charging on run resurrection.
- Candidate pricing ($0.00005 synthetic start + $0.02 / page checked) in `pricing.candidate.json` / `PRICING.md`. Default + max memory 512 MB.

#### Backward compatibility

- `emitBaseline` re-added to `input_schema.json` as a **deprecated, inert boolean** (`default: false`, matching v0.1). `additionalProperties` stays `false`. The v0.2 runtime never reads it — behaviour is driven solely by `outputMode` (`CHANGES_ONLY` / `ALL_CHECKS`). This keeps the existing scheduled task input (`ZNHygpx7vg26JTQTD`, which still sends `emitBaseline`) valid after deployment.
- Deployment follow-up (not done here — live untouched): task `ZNHygpx7vg26JTQTD` pins `memoryMbytes: 4096`, overriding the Actor default. After a v0.2 smoke test, change it to `512` and preferably drop `emitBaseline` from its stored input. The schema compatibility stays regardless.

#### Tests

- `tests/test_schema_compat.py` — legacy task input validates; `emitBaseline` true/false validate; `emitBaseline` has no effect on output (`diff_page` has no such parameter); unknown fields still rejected; `additionalProperties` is `false`.
- `tests/test_core.py` — extract (script strip, price detect, tracking-param strip), diff (baseline, unchanged, added/removed/content, price-changed, keyword filter, ALL_CHECKS), state migration (v1→v2, unmigratable drop, malformed reject).
- `tests/test_security.py` — schemes, credentials, deterministic normalisation, localhost/private/link-local/IPv6-loopback/`.local` blocked, DNS failure.
- `tests/test_fetcher.py` — 200 OK, 404→http_error, timeout→timeout, oversized (header + stream).
- `tests/test_billing.py` — charge once per URL, resurrection dedup, charge-failure tolerance, budget limit.
- `tests/test_demo.py` — deterministic fixture walk-through (baseline → PRICE_CHANGED + ADDED_TEXT + REMOVED_TEXT + CONTENT_CHANGED → no duplicate on repeat).
