openvibers/openvibe.compare Commits
AGENTS.md: the context an agent starts from (#10)
Changes
nnew file mode 100644@@@ -0,0 +1,126 @@++# AGENTS.md — OpenVibe.Compare++++## Project overview++++Public, source-linked comparisons for the OpenVibe network: AI models (price and measured Bench++quality/speed), AI coding agents (Codes harnesses plus verified Bench runs) and OpenVibe plans++(Billing's public plan list). Node.js/Express, fed entirely by other services' public APIs, **no++database, no accounts, no write API**. Server-rendered pages; `openvibe-shared` for the shell.++++- Origin `https://openvibe.compare`, port `5280`, unit `openvibe-compare.service`.++- One rule above all: **missing evidence is not zero**. A thing without a measurement is unranked,++ never scored 0; a failed feed shows an explicit unavailable state and fills nothing in.++++## What it owns++++- `GET /` — the comparison index and search.++- `GET /comparisons/:slug` — one comparison; weight overrides via `?w.<criterion>=`.++- `GET /things/:id` — one thing across every comparison that contains it; `/models/:org/:model`++ 301s here.++- `GET /api/v1/comparisons[/:slug]` — the same data as JSON, CORS-allowed for the origins in++ [server/origins.json](server/origins.json).++- `robots.txt`, `sitemap.xml`, `llms.txt`, Dataset JSON-LD, and `Accept: text/markdown` negotiation —++ all generated from the same data, never hand-written separately.++++It owns no private data, no paid execution, no builder or version-history write (reserved in the++schema with no API in v1).++++## Who it reads++++Five public endpoints, all in [server/data.js](server/data.js) `ENDPOINTS`:++++| Feed | What |++|---|---|++| `api.openvibe.exchange/api/v1/exchange/market` | dated offers and checked prices (AI Exchange) |++| `bench.openvibe.services/api/v1/boards/chat.arena` | Arena Elo and median response time (OpenVibe.Bench) |++| `bench.openvibe.services/api/v1/runs?kind=codes.patch` / `codes.ticket` | verified Bench runs (OpenVibe.Bench) |++| `openvibe.codes/api/v1/harnesses` | coding harness catalog (OpenVibe.Codes) |++| `billing.openvibe.network/api/v1/plans` | published plans (OpenVibe.Billing) |++++It does not call AI, Live, Network or any authenticated API. Nothing here writes anywhere.++++## Repository map++++- **Entry:** [server/index.js](server/index.js) — listens on `PORT || 5280`, graceful SIGTERM.++- **App/routes:** [server/app.js](server/app.js) — security headers, static `public/`, `/shared`,++ health/ready, `/release.json`, `/api/v1`, pages, 404 and 503.++- **Data:** [server/data.js](server/data.js) — fetches the five feeds (8 s timeout), builds the three++ comparisons, coalesces concurrent reads and caches for ten minutes; a failed feed becomes `null`.++- **Views:** [server/views.js](server/views.js) — HTML, markdown, escaping, JSON-LD, the method text.++- **Ranking:** [public/rank.js](public/rank.js) — the one ranking implementation, required by both the++ server views and the browser.++- **Browser:** [public/comparison.js](public/comparison.js), [public/compare-account.js](public/compare-account.js),++ [public/compare.css](public/compare.css).++- **Deploy:** [deploy/systemd/openvibe-compare.service](deploy/systemd/openvibe-compare.service),++ [deploy/nginx/openvibe.compare.conf](deploy/nginx/openvibe.compare.conf).++++## Commands++++```bash++npm ci++npm start # node server/index.js, http://127.0.0.1:5280++npm test # node --test test/*.test.js++ov test # the harness's sharded run of what the change reaches++node --check <file.js>++```++++There is no `dev` script and no build step: edit `server/` or `public/` and restart.++++## Testing++++`test/compare.test.js` uses Node's built-in test runner with fixture feeds. It pins the rules that++matter: every measurement is finite, dated, sourced, and references a known thing and criterion;++missing evidence stays missing; exact model identity only; ranking handles direction, equal values,++zero/invalid weights and all-zero weights (unranked); pages and JSON agree. `ov test` runs the same++file plus the repository checks the harness adds.++++## Conventions++++- **Compact, single-line JS** is the house style here (`server/app.js`, `server/data.js`); match the++ file you edit rather than reformatting it.++- CommonJS, `'use strict'`, 4 spaces in the newer data helpers, no linter.++- All user-visible data is escaped with `views.esc`; JSON is embedded with `json()` (escapes `<`).++- Reuse `openvibe-shared/kit`, `openvibe-shared/shell` and `openvibe-shared/serve` instead of new++ layout or asset code.++++## Data and provenance rules++++- A result needs a finite number, a parseable date, and an `https:` source URL; otherwise it is not++ added ([server/data.js](server/data.js) `result`).++- Prices: the cheapest dated offer by input + output, both from the same provider. Provider feedback++ and observation dates are labelled; no price is estimated.++- Arena quality: at least ten judged games and **exact** model identity (`r.model === id`); no fuzzy++ joins, because a fuzzy join silently compares a different model revision.++- Coding agents: only `provenance === 'verified'` runs; benchmark version and model form separate++ criteria; newest verified run per harness/cohort.++- Plans: packages and memberships have separate units and are never ranked together.++- Ranking: min–max within the comparison then a weighted mean on 0–100; equal values get 50;++ criteria with no measurements are excluded; all-zero weights leave everything unranked.++++## Deploy++++No database and no service credentials. The unit runs `/opt/openvibe.compare/server/index.js` on++`127.0.0.1:5280`, env `/etc/openvibe/compare.env`. The README's launch path predates ovhost:++`register-compare.py` registers port 5280 with a lifecycle override until a Contracts manifest is++released, then `merge-deploy.sh` and `ov-vhost-install deploy/nginx/openvibe.compare.conf`. Keep the++vhost and unit in sync with [deploy/](deploy/); a docs-only change needs no deploy.++++## Pitfalls++++- **Do not invent a write path.** Builder and version history are deliberately absent from v1.++- **Do not cache a failed feed into an old value.** It is `null` for that read; the next request++ retries, and the comparison lists it as unavailable.++- **Do not hand-write `sitemap.xml`, `llms.txt` or JSON-LD.** They are generated from `getData()` so++ they cannot drift from the pages.++- **Keep `public/rank.js` framework-free and dual-use** (server `require` and browser global); the++ test loads it both ways.++- **CORS is an exact allow-list** ([server/origins.json](server/origins.json)); no `*` and no++ credentials.++- **`Accept: text/markdown`** on `/`, `/comparisons/:slug` and `/things/:id` is part of the product;++ keep it when touching the views.++- **Do not remove the 503 handler**: an unexpected feed failure must answer explicitly, not 500.++++## Documentation++++The [README.md](README.md) is the whole product brief (there is no `docs/`). Contracts, if a manifest++is added later, live in OpenVibe.Contracts.nnew file mode 100644@@@ -0,0 +1 @@
new file mode 100644
new file mode 100644
@@ -0,0 +1,126 @@
@@ -0,0 +1,126 @@
+# AGENTS.md — OpenVibe.Compare
+
+## Project overview
+
+Public, source-linked comparisons for the OpenVibe network: AI models (price and measured Bench
+quality/speed), AI coding agents (Codes harnesses plus verified Bench runs) and OpenVibe plans
+(Billing's public plan list). Node.js/Express, fed entirely by other services' public APIs, **no
+database, no accounts, no write API**. Server-rendered pages; `openvibe-shared` for the shell.
+
+- Origin `https://openvibe.compare`, port `5280`, unit `openvibe-compare.service`.
+- One rule above all: **missing evidence is not zero**. A thing without a measurement is unranked,
+ never scored 0; a failed feed shows an explicit unavailable state and fills nothing in.
+
+## What it owns
+
+- `GET /` — the comparison index and search.
+- `GET /comparisons/:slug` — one comparison; weight overrides via `?w.<criterion>=`.
+- `GET /things/:id` — one thing across every comparison that contains it; `/models/:org/:model`
+ 301s here.
+- `GET /api/v1/comparisons[/:slug]` — the same data as JSON, CORS-allowed for the origins in
+ [server/origins.json](server/origins.json).
+- `robots.txt`, `sitemap.xml`, `llms.txt`, Dataset JSON-LD, and `Accept: text/markdown` negotiation —
+ all generated from the same data, never hand-written separately.
+
+It owns no private data, no paid execution, no builder or version-history write (reserved in the
+schema with no API in v1).
+
+## Who it reads
+
+Five public endpoints, all in [server/data.js](server/data.js) `ENDPOINTS`:
+
+| Feed | What |
+|---|---|
+| `api.openvibe.exchange/api/v1/exchange/market` | dated offers and checked prices (AI Exchange) |
+| `bench.openvibe.services/api/v1/boards/chat.arena` | Arena Elo and median response time (OpenVibe.Bench) |
+| `bench.openvibe.services/api/v1/runs?kind=codes.patch` / `codes.ticket` | verified Bench runs (OpenVibe.Bench) |
+| `openvibe.codes/api/v1/harnesses` | coding harness catalog (OpenVibe.Codes) |
+| `billing.openvibe.network/api/v1/plans` | published plans (OpenVibe.Billing) |
+
+It does not call AI, Live, Network or any authenticated API. Nothing here writes anywhere.
+
+## Repository map
+
+- **Entry:** [server/index.js](server/index.js) — listens on `PORT || 5280`, graceful SIGTERM.
+- **App/routes:** [server/app.js](server/app.js) — security headers, static `public/`, `/shared`,
+ health/ready, `/release.json`, `/api/v1`, pages, 404 and 503.
+- **Data:** [server/data.js](server/data.js) — fetches the five feeds (8 s timeout), builds the three
+ comparisons, coalesces concurrent reads and caches for ten minutes; a failed feed becomes `null`.
+- **Views:** [server/views.js](server/views.js) — HTML, markdown, escaping, JSON-LD, the method text.
+- **Ranking:** [public/rank.js](public/rank.js) — the one ranking implementation, required by both the
+ server views and the browser.
+- **Browser:** [public/comparison.js](public/comparison.js), [public/compare-account.js](public/compare-account.js),
+ [public/compare.css](public/compare.css).
+- **Deploy:** [deploy/systemd/openvibe-compare.service](deploy/systemd/openvibe-compare.service),
+ [deploy/nginx/openvibe.compare.conf](deploy/nginx/openvibe.compare.conf).
+
+## Commands
+
+```bash
+npm ci
+npm start # node server/index.js, http://127.0.0.1:5280
+npm test # node --test test/*.test.js
+ov test # the harness's sharded run of what the change reaches
+node --check <file.js>
+```
+
+There is no `dev` script and no build step: edit `server/` or `public/` and restart.
+
+## Testing
+
+`test/compare.test.js` uses Node's built-in test runner with fixture feeds. It pins the rules that
+matter: every measurement is finite, dated, sourced, and references a known thing and criterion;
+missing evidence stays missing; exact model identity only; ranking handles direction, equal values,
+zero/invalid weights and all-zero weights (unranked); pages and JSON agree. `ov test` runs the same
+file plus the repository checks the harness adds.
+
+## Conventions
+
+- **Compact, single-line JS** is the house style here (`server/app.js`, `server/data.js`); match the
+ file you edit rather than reformatting it.
+- CommonJS, `'use strict'`, 4 spaces in the newer data helpers, no linter.
+- All user-visible data is escaped with `views.esc`; JSON is embedded with `json()` (escapes `<`).
+- Reuse `openvibe-shared/kit`, `openvibe-shared/shell` and `openvibe-shared/serve` instead of new
+ layout or asset code.
+
+## Data and provenance rules
+
+- A result needs a finite number, a parseable date, and an `https:` source URL; otherwise it is not
+ added ([server/data.js](server/data.js) `result`).
+- Prices: the cheapest dated offer by input + output, both from the same provider. Provider feedback
+ and observation dates are labelled; no price is estimated.
+- Arena quality: at least ten judged games and **exact** model identity (`r.model === id`); no fuzzy
+ joins, because a fuzzy join silently compares a different model revision.
+- Coding agents: only `provenance === 'verified'` runs; benchmark version and model form separate
+ criteria; newest verified run per harness/cohort.
+- Plans: packages and memberships have separate units and are never ranked together.
+- Ranking: min–max within the comparison then a weighted mean on 0–100; equal values get 50;
+ criteria with no measurements are excluded; all-zero weights leave everything unranked.
+
+## Deploy
+
+No database and no service credentials. The unit runs `/opt/openvibe.compare/server/index.js` on
+`127.0.0.1:5280`, env `/etc/openvibe/compare.env`. The README's launch path predates ovhost:
+`register-compare.py` registers port 5280 with a lifecycle override until a Contracts manifest is
+released, then `merge-deploy.sh` and `ov-vhost-install deploy/nginx/openvibe.compare.conf`. Keep the
+vhost and unit in sync with [deploy/](deploy/); a docs-only change needs no deploy.
+
+## Pitfalls
+
+- **Do not invent a write path.** Builder and version history are deliberately absent from v1.
+- **Do not cache a failed feed into an old value.** It is `null` for that read; the next request
+ retries, and the comparison lists it as unavailable.
+- **Do not hand-write `sitemap.xml`, `llms.txt` or JSON-LD.** They are generated from `getData()` so
+ they cannot drift from the pages.
+- **Keep `public/rank.js` framework-free and dual-use** (server `require` and browser global); the
+ test loads it both ways.
+- **CORS is an exact allow-list** ([server/origins.json](server/origins.json)); no `*` and no
+ credentials.
+- **`Accept: text/markdown`** on `/`, `/comparisons/:slug` and `/things/:id` is part of the product;
+ keep it when touching the views.
+- **Do not remove the 503 handler**: an unexpected feed failure must answer explicitly, not 500.
+
+## Documentation
+
+The [README.md](README.md) is the whole product brief (there is no `docs/`). Contracts, if a manifest
+is added later, live in OpenVibe.Contracts.
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@