Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ Inside the image, sites live at `/opt/WebSyn/<site>/`. The path predates the ren
# fresh clone
./scripts/fetch_assets.sh # pulls assets from HF
./scripts/build.sh # docker build -t webharbor:dev .
docker run -d -p 8101:8101 -p 40000-40027:40000-40027 webharbor:dev
docker run -d -p 8101:8101 -p 40000-40028:40000-40028 webharbor:dev
```

Or use the published image directly:

```bash
docker run -d -p 8101:8101 -p 40000-40027:40000-40027 \
docker run -d -p 8101:8101 -p 40000-40028:40000-40028 \
battalion7244/webharbor:latest
```

Sites are on `40000`-`40027` in the order declared by `SITES=( ... )` in `websyn_start.sh`. Control plane:
Sites are on `40000`-`40028` in the order declared by `SITES=( ... )` in `websyn_start.sh`. Control plane:

| Method | Path | Purpose |
|--------|---------------------|-------------------------------------------|
Expand Down Expand Up @@ -136,13 +136,13 @@ python3 -m py_compile sites/<site>/app.py

# 3. run on alt ports (don't collide with anything you already have running)
docker run -d --rm --name wh-test \
-p 8201:8101 -p 41000-41027:40000-40027 webharbor:dev
-p 8201:8101 -p 41000-41028:40000-40028 webharbor:dev

# 4. control plane healthy, all sites alive
curl -s http://localhost:8201/health | python3 -m json.tool | head

# 5. every site renders 200
for p in $(seq 41000 41027); do
for p in $(seq 41000 41028); do
curl -so /dev/null -w "$p:%{http_code}\n" http://localhost:$p/
done

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ The full agent guide is loaded above via `@AGENTS.md`. The notes below apply onl

## Existing containers

If a container is already running on `:8101` / `:40000-40027`, treat it as the user's working environment — don't `docker stop` or `docker rm` it without explicit confirmation. Spin up your test container under a different name on alt ports (`:8201`, `:41000-41027`).
If a container is already running on `:8101` / `:40000-40028`, treat it as the user's working environment — don't `docker stop` or `docker rm` it without explicit confirmation. Spin up your test container under a different name on alt ports (`:8201`, `:41000-41028`).
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ git clone https://github.com/<you>/webharbor && cd webharbor
./scripts/fetch_assets.sh # pull current assets
./scripts/new_site.py mywebsite # OR edit an existing site
./scripts/build.sh && docker run -d --rm \
-p 8101:8101 -p 40000-40027:40000-40027 webharbor:dev
-p 8101:8101 -p 40000-40028:40000-40028 webharbor:dev
# iterate locally...

./scripts/extract_assets.sh ../webharbor-static-pr/ # split assets out
Expand Down
13 changes: 11 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# WebHarbor — slim, self-contained image.
# 28 Flask mirror sites + control plane on :8101.
# 29 Flask mirror sites + control plane on :8101.

FROM python:3.12-slim-bookworm

Expand Down Expand Up @@ -92,6 +92,15 @@ os.makedirs('instance_seed', exist_ok=True); \
shutil.copy2('instance/rotten_tomatoes.db', 'instance_seed/rotten_tomatoes.db'); \
print('Rotten Tomatoes seed DB generated at build time.')" && rm -rf /opt/WebSyn/rotten_tomatoes/instance

EXPOSE 8101 40000-40027
# AccuWeather uses genuine captured UI assets and freezes its deterministic seed.
RUN test -n "$(ls -A /opt/WebSyn/accuweather/static/images)" && \
cd /opt/WebSyn/accuweather && rm -rf instance instance_seed && python3 -c "\
import app; \
import os, shutil; \
os.makedirs('instance_seed', exist_ok=True); \
shutil.copy2('instance/accuweather.db', 'instance_seed/accuweather.db'); \
print('AccuWeather seed DB generated at build time.')" && rm -rf /opt/WebSyn/accuweather/instance

EXPOSE 8101 40000-40028

CMD ["/opt/websyn_start.sh"]
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,17 @@ WebHarbor takes a different approach. We leverage coding agent (e.g., Claude Cod
- **Deep features unlocked** — carts, checkouts, accounts, all fully testable
- **Evolving** — harder tasks drive richer mirrors; the environment grows with agents
- **RL-ready** — sub-second database resets between rollouts
- **Community-driven** — 28 sites today, scaling to 100+ together
- **Community-driven** — 29 sites today, scaling to 100+ together

## 🚀 Quickstart

One command to run all web environments:

```bash
docker run -p 8101:8101 -p 40000-40027:40000-40027 battalion7244/webharbor:latest
docker run -p 8101:8101 -p 40000-40028:40000-40028 battalion7244/webharbor:latest
```

Then point your agent at `http://localhost:40000` through `http://localhost:40027` to explore 28 local mirrors of WebVoyager sites: `Allrecipes, Amazon, Apple, ArXiv, BBC News, Booking, GitHub, Google Flights, Google Maps, Google Search, Hugging Face, Wolfram Alpha, Cambridge Dictionary, Coursera, ESPN, Merriam-Webster, IKEA, Phys.org, Target, TED, Ohio State University, Rotten Tomatoes, Compass, Walmart Careers, FedEx, WebMD Doctor, Healthline, and Kaggle`.
Then point your agent at `http://localhost:40000` through `http://localhost:40027` to explore 29 local mirrors of WebVoyager sites: `Allrecipes, Amazon, Apple, ArXiv, BBC News, Booking, GitHub, Google Flights, Google Maps, Google Search, Hugging Face, Wolfram Alpha, Cambridge Dictionary, Coursera, ESPN, Merriam-Webster, IKEA, Phys.org, Target, TED, Ohio State University, Rotten Tomatoes, Compass, Walmart Careers, FedEx, WebMD Doctor, Healthline, Kaggle, and AccuWeather`.

For sub-second reset between rollouts, expose the control plane and call `/reset/<site>`:

Expand Down
2 changes: 1 addition & 1 deletion agent_demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export OPENAI_BASE_URL=https://api.openai.com/v1 # or your Azure / vLLM endpoi

## Run a task

WebHarbor must already be running locally (`docker run -p 8101:8101 -p 40000-40027:40000-40027 battalion7244/webharbor:latest`).
WebHarbor must already be running locally (`docker run -p 8101:8101 -p 40000-40028:40000-40028 battalion7244/webharbor:latest`).

Run a single task from a site's `tasks.jsonl`:

Expand Down
1 change: 1 addition & 0 deletions control_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
'ikea', 'phys_org', 'target', 'ted',
'osu', 'rotten_tomatoes', 'compass', 'walmart_careers',
'fedex', 'webmd_doctor', 'healthline', 'kaggle',
'accuweather',
]
BASE_PORT = 40000
WEBSYN_DIR = '/opt/WebSyn'
Expand Down
54 changes: 54 additions & 0 deletions review-reports/ACCUWEATHER-FINAL-AUDIT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# AccuWeather final task and UI audit

Audit date: 2026-09-09. Branch: `add-accuweather-mirror`.

The site was reset before every task and browser cookies were cleared. Every run began at `http://localhost:41024/` and used Playwright visible-element locators to search, open results, select tabs, sign in, change preferences, and submit forms. No task used a direct destination URL, database lookup, or source-code answer.

| Task | Result | Flow checked | Screenshot |
|---|---|---|---|
| AccuWeather--0 | Pass | Search → Phoenix current conditions | `outputs/accuweather-review/0-99-final.png` |
| AccuWeather--1 | Pass | Ambiguous Portland search → Maine result | `outputs/accuweather-review/1-99-final.png` |
| AccuWeather--2 | Pass | Search → Seattle → Hourly | `outputs/accuweather-review/2-99-final.png` |
| AccuWeather--3 | Pass | Search → Miami → Daily | `outputs/accuweather-review/3-99-final.png` |
| AccuWeather--4 | Pass | Search and compare Austin / Denver | `outputs/accuweather-review/4-99-final.png` |
| AccuWeather--5 | Pass | Springfield distractors → Missouri → Air Quality | `outputs/accuweather-review/5-99-final.png` |
| AccuWeather--6 | Pass | Login → search → save Seattle → account | `outputs/accuweather-review/6-99-final.png` |
| AccuWeather--7 | Pass | Login → Boston → remove → account | `outputs/accuweather-review/7-99-final.png` |
| AccuWeather--8 | Pass | Login → Chicago → alert form submit | `outputs/accuweather-review/8-99-final.png` |
| AccuWeather--9 | Pass | Login → Settings → Celsius → New York | `outputs/accuweather-review/9-99-final.png` |
| AccuWeather--10 | Pass | Postal search → San Francisco | `outputs/accuweather-review/10-99-final.png` |
| AccuWeather--11 | Pass | Search → radar → current weather | `outputs/accuweather-review/11-99-final.png` |
| AccuWeather--12 | Pass | Compare two Air Quality pages | `outputs/accuweather-review/12-99-final.png` |
| AccuWeather--13 | Pass | Search → Boston seven-day forecast | `outputs/accuweather-review/13-99-final.png` |
| AccuWeather--14 | Pass | London current → Air Quality | `outputs/accuweather-review/14-99-final.png` |
| AccuWeather--15 | Pass | Login → save two cities → account | `outputs/accuweather-review/15-99-final.png` |
| AccuWeather--16 | Pass | Portland Oregon / Maine comparison | `outputs/accuweather-review/16-99-final.png` |
| AccuWeather--17 | Pass | Search → Toronto hourly forecast | `outputs/accuweather-review/17-99-final.png` |
| AccuWeather--18 | Pass | Register → save Atlanta → account | `outputs/accuweather-review/18-99-final.png` |
| AccuWeather--19 | Pass | Phoenix / New Orleans comparison | `outputs/accuweather-review/19-99-final.png` |

## Hardening results

- De-leak: search results disclose only location identity and postal code, never current, hourly, daily, or air-quality answers. The prompt wording, ordering, labels, and cards do not reveal computed answers.
- Distractors: ambiguous Portland and Springfield queries return two and three same-name locations respectively; the 20-location catalog also supplies realistic cross-city comparison choices.
- Catalog breadth: 20 locations, 140 daily rows, and 240 hourly rows cover United States, Canada, and United Kingdom conditions and all captured icon types.
- Cross-field consistency: current temperatures, derived forecasts, unit conversion, conditions, location identity, saved locations, and alerts use relational records shared by all views.
- Leak archetypes checked: direct answer in prompt; target first by artificial ordering; pre-sorted winner; count label; detail on result card; unique target without distractors; answer in URL/slug; hidden data attribute; accessible-name answer; placeholder answer; success without mutation; visit-only verification; unrelated-state acceptance. None were found.
- Five hard multi-step tasks: 4, 5, 12, 13, and 19. Tasks 6–9, 15, and 18 additionally verify persistent state changes.

## Responsive and asset checks

Home, search, current, hourly, and radar pages were checked at 1440, 390, and 320 CSS pixels (15 page/viewport combinations). All had zero horizontal overflow and zero broken images. The committed screenshots below are representative; the full evidence set and structured step log are in `outputs/accuweather-review/`.

- `review-reports/assets/accuweather-homepage-1440.png`
- `review-reports/assets/accuweather-homepage-390.png`
- `review-reports/assets/accuweather-homepage-320.png`

The Solis font, GPS icon, and weather condition SVGs were captured from the live AccuWeather homepage asset inventory. No placeholder image is used.

## Reset proof

After the final task audit, `POST /reset/accuweather` completed ready and both files had MD5 `2701fb49e1edc178024785e6f8601870`:

- `/opt/WebSyn/accuweather/instance/accuweather.db`
- `/opt/WebSyn/accuweather/instance_seed/accuweather.db`
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions sites/accuweather/.build-generated-seed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This site rebuilds instance_seed/accuweather.db deterministically at image build time (see Dockerfile); the HF bundle carries no seed DB.
1 change: 1 addition & 0 deletions sites/accuweather/.requires-images
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This site requires static/images (captured AccuWeather weather icons + GPS icon) from the pinned Hugging Face asset bundle.
73 changes: 73 additions & 0 deletions sites/accuweather/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# AccuWeather mirror

An offline Flask mirror of accuweather.com used as a deterministic benchmark
fixture. It serves 20 locations with current conditions, hourly and daily
forecasts, radar, air quality, accounts, saved locations, alert preferences and
a temperature-unit setting.

## Scope and deliberate simplifications

- **No JavaScript.** No template loads a script; every interaction is a plain
form or link, so the site degrades perfectly without JS.
- **Radar is decorative.** `/radar/<slug>` renders a CSS gradient, not a map
tile or a real radar image. The heading names the city; nothing else on the
page is a fact a task may depend on.
- **Forecasts are generated from the current temperature** (`app.py`,
`seed_database`): today's high is `current - 2`, the hourly peak is 4 PM for
every city, and the lowest daily low is Saturday for every city. This makes
the list-scan pattern identical across cities. It is deterministic and
self-consistent, but it is not realistic per-city weather.
- **Content is synthetic.** No value is a real-world fact, so no task can be
answered from model knowledge.

## Seed determinism

`instance_seed/accuweather.db` is rebuilt at image build time (see
`.build-generated-seed` and the Dockerfile step). The four benchmark users'
password hashes are frozen constants in `app.py`
(`BENCHMARK_PASSWORD_HASHES`) because `generate_password_hash` salts randomly,
which would otherwise make the seed a different file on every build.

The seed is byte-reproducible **within one SQLite runtime**. Across SQLite
versions the file bytes can differ while every row is identical; compare the
row-level catalog fingerprint (`verify/verify_lib.py`, `CATALOG_FINGERPRINT`)
rather than the file's md5 when checking across environments.

## Assets and provenance

Per-asset source URLs are recorded in `provenance.json`. The seven SVG weather
and GPS icons are captured from accuweather.com and are fetched from the pinned
Hugging Face asset bundle (`.requires-images`); they are not tracked in git.

### Font notice

`static/fonts/Solis-Regular.woff2` (22832 bytes, sha256
`e23435d0e387ffe2c818e1f500d0e58e7e996251871fad7df54b38404cc3a384`) is the
webfont served by accuweather.com. Its SFNT name table is shipped **verbatim
and unmodified**; in particular:

- name ID 0 (copyright): `© Copyright AccuWeather, 2019. All rights reserved.`
- name ID 8 (manufacturer): `Type Network`
- name ID 9 (designer): `Laura Meseguer with Dyana Weissman`
- name ID 11 (vendor URL): `http://www.typenetwork.com/`
- name ID 14 (license URL): `http://www.loyalkaspar.com`

The file carries no name ID 13 (license description) upstream, and no licence
grant accompanies it. It is included only to reproduce the visual proportions
of the original page in an offline research fixture.

**Non-affiliation.** This mirror is not affiliated with, endorsed by, or
sponsored by AccuWeather, Inc. or Type Network. "AccuWeather" and "RealFeel"
are trademarks of AccuWeather, Inc., used here only to identify the site being
mirrored.

**To remove the font**, delete `static/fonts/Solis-Regular.woff2` and the
`@font-face` rule at the top of `static/css/site.css`. The stylesheet already
declares the fallback stack `Solis, Arial, sans-serif`, so the page renders in
Arial with no further change.

## Grading

Deterministic verifiers for all 20 tasks live in `verify/`; see
`verify/README.md` for the contract and `verify/tests/` for the unit harness
and the real-browser matrix.
3 changes: 3 additions & 0 deletions sites/accuweather/_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Per-site health probe (optional, called by control_server)."""
def health():
return {"ok": True, "site": "accuweather"}
Loading