Add the write-action layer and a daily upstream compatibility check
Upstream compatibility / compat (push) Failing after 7s
Validate / hassfest (push) Failing after 25s
Validate / HACS (push) Failing after 17s
Validate / pytest (push) Successful in 9s

actions.py covers skip, donate, cart add/remove, subscriptions and vacation
holds. Every mutating endpoint on the site is guarded by rotating per-render
tokens, so each action re-derives them from a live page rather than storing
anything; skip additionally compares the date the server states in its
confirmation against the date it was asked to skip, and refuses on a mismatch.
All actions default to dry_run.

tools/compat.py records what the integration assumes about a site that offers
no API and no stability contract, and CI asserts it daily. Only the
unauthenticated surface is covered: checking the rest would mean putting a
personal account password in public repo secrets.
This commit is contained in:
flan
2026-08-03 20:02:58 +00:00
parent 3e880ff2c1
commit 213c16d98e
6 changed files with 776 additions and 6 deletions
+37
View File
@@ -79,6 +79,43 @@ Two invariants hold against the portal's own arithmetic, and tests assert both:
- `open_order_free_delivery_remaining` reaching `0.00` always coincides with a
`0.00` delivery fee
## Upstream compatibility
freshharvest.com has no API and no stability contract — this integration reads
HTML and posts to form endpoints, so a redesign can change what a value *means*
without changing its shape. [tools/compat.py](tools/compat.py) records every
assumption and CI asserts them against the live site daily, refreshing this
table and opening an issue on drift.
<!-- COMPAT:START -->
_Last checked 2026-08-03._
| Area | Assumption | Status | Detail |
| --- | --- | --- | --- |
| Login | `/s/popup/login` serves the form | ✅ | 2273 bytes |
| Login | hidden `LoginSecurity` is minted | ✅ | 154 chars |
| Login | hidden `SubmitToken` is minted | ✅ | 174 chars |
| Login | posts to `/s/submit/login` | ✅ | /s/submit/login |
| Login | field `LoginEmail` present | ✅ | |
| Login | field `LoginPassword` present | ✅ | |
| Catalogue | Algolia credentials readable from site JS | ✅ | app id + search key found |
| Catalogue | index name readable | ✅ | dev_FullTest |
| Catalogue | index returns a plausible catalogue | ✅ | 946 records |
| Catalogue | record field `ID` | ✅ | present |
| Catalogue | record field `Name` | ✅ | present |
| Catalogue | record field `Price` | ✅ | present |
| Catalogue | record field `Measurement` | ✅ | present |
| Catalogue | record field `Categories` | ✅ | present |
| Endpoints | cart add/remove URL shape unchanged | ✅ | /p/Ajax/order-manage/ |
| Endpoints | popup route is `/x/popup/{type}/{token}` | ✅ | found |
<!-- COMPAT:END -->
Only the unauthenticated surface is checked here. The authenticated contract —
dashboard markup, cart add hashes, skip popups, subscribe forms — needs a real
session, and the only way to give public CI one is to put a personal grocery
account's password in repo secrets. That belongs in a job on a host that already
has credential access, not here.
## How it works
Fresh Harvest is not on Shopify, Farmigo, or Local Line — the page metadata