openvibers/openvibe.tours Commits

AGENTS.md: the context an agent starts from (#156)

OP OpenVibers committed 1 days ago · c7c1b6ee0b · 2 files, +99 −0

Adds `AGENTS.md` (121 lines) as the context an agent starts from, plus `CLAUDE.md` (`@AGENTS.md`).

It covers: the six Tours surfaces and their open-data sources; what Tours caches and what stays with
Live/Rent/Media/AI/Network; source-and-fetch-time provenance; the PostgreSQL tables and migrations;
SSO and the cross-site cookie rule; deploy; the test runner and coverage; conventions; and the
no-invented-content, no-affiliate/no-payment, TTS-off and open-data-terms pitfalls.

Checked: every cited path exists on main; no code, dependency or version changed.

Changes

ddiff --git a/AGENTS.md b/AGENTS.md
nnew file mode 100644
iindex 0000000..362224a
---- /dev/null
++++ b/AGENTS.md
@@@ -0,0 +1,98 @@
++# AGENTS.md — OpenVibe.Tours
++
++## Project overview
++
++OpenVibe.Tours (`https://openvibe.tours`) is the network's travel room: cities and places from open
++data, IRL live tours and stays from the network's public APIs, self-guided routes with cited stops,
++and a signed-in person's trips. Everything is server-rendered and works without JavaScript.
++
++- **Entry:** `server/index.js`; app in `server/http/router.js`; port `5170`, service id `tours`.
++- **Sources:** `server/sources/` — `osm.js` (Overpass), `wikidata.js`, `wikivoyage.js`/
++ `wikivoyage-client.js`, `live.js` (Live's public list), `rent.js` (Rent's public listings),
++ `openverse.js`, `http.js`.
++- **Places:** `server/places/` — `discover.js`, `is-place.js`, `narrative.js` (AI summary + attribution),
++ `photo-cache.js` (licensed images to Media), `clip.js`.
++- **Jobs:** `server/jobs/` — `cities.js`, `places.js`, `routes.js`, `live.js`, `stays.js`, `index.js`.
++- **API/pages:** `server/http/api.js` (`/api/v1`), `principal.js`, `pages.js`, `discovery.js`;
++ views in `server/views/index.js`/`labels.js`, rendering in `server/render/layout.js`/`html.js`.
++ `public/js/tours.js` is the only own browser script (MapLibre only on the route page); own CSS in
++ `public/css`.
++- **Seeds:** `seed/`, `scripts/seed.js`; status and counts in `STATUS.json`.
++
++## Owns, does not own
++
++Owns its cached open data: `tours_cities` (`cty_`), `tours_city_sources`, `tours_places` (`plc_`),
++`tours_routes` (`rte_`), `tours_route_stops`, the trips/shares tables and its outbox. Every row keeps
++the source URL and the time fetched.
++
++Does not own: Live, Rent, Media and AI — Tours reads Live's and Rent's public pages/lists; licensed
++place images are copied to Media with the `media.object.upload` service scope; sourced summaries use
++AI (`ai.run.create`, `ai.run.read`) when `OV_AI_INTERNAL_URL` is configured. Both keep the original
++source and attribution on the place page. Identity/roles are Network's (OAuth client `tours`, PKCE
++S256, canonical `usr_…`). OpenStreetMap, Wikivoyage, Wikidata and Commons are external open data,
++cached with source and fetched time — never scraped past their terms. Tours never takes a payment and
++carries **no affiliate link**.
++
++## Who calls it, what it calls
++
++People use the pages/API with the `tours_at` session cookie or a Network Bearer token. Trips and saved
++places are the person's own; a share link is read-only. Tours calls Network (SSO, JWKS, identity),
++Live's public stream/VOD list, Rent's public listings, Media (image upload), AI (summaries when
++configured), and the open-data endpoints (Wikidata, Wikivoyage, Overpass, Commons, Openverse) through
++`server/sources/`. It calls no service with a person's write; the public reads are cacheable, a person's
++page is `private, no-store`.
++
++## Database and migrations
++
++PostgreSQL through `openvibe-sdk/db`; migrations `migrations/0001..0011` (initial, place sources,
++search index, English names, shares/defaults, outbox, discoverable/notable places, photo retries) are
++applied at boot with the owner role. Tests use PGlite; `npm run test:pg` runs them on PostgreSQL.
++Never edit a migration that has run. A job records what it fetched; a failed source leaves the cached
++row and its fetched time rather than inventing content.
++
++## Auth
++
++Network SSO (RS256 JWT + `tours_at` cookie) and offline JWKS verification. A write made with the
++session cookie must come from `https://openvibe.tours`, else `403 tours.cross_site`. Every person
++acts only on their own trips and saved places; a share link is an unguessable read-only token.
++`/internal/events` is HMAC-verified and loopback-only.
++
++## Deploy
++
++`deploy/systemd/openvibe-tours.service` (port `5170`, env `/etc/openvibe/tours.env`, checkout
++`/opt/openvibe.tours`) and `deploy/nginx/openvibe.tours.conf` (the forms zone covers `/trips`);
++`sudo ovhost deploy tours` is the production path and `sudo ovhost rollback tours --to <sha>` the
++rollback.
++
++## Commands and tests
++
++```bash
++npm ci
++npm run dev # http://127.0.0.1:5170
++npm test # every test/*.test.js, PGlite + in-process mocks
++npm test -- places routes # only matching files
++npm run test:pg # the same on PostgreSQL
++npm run seed # seed cities/places from the open sources
++```
++
++`test/run.js` uses `openvibe-shared/test-runner` (90 s, serial); temp PGlite plus in-process Network,
++Live, Chat, Today and OSM/Ticketmaster fixtures, never the network. Tests cover the sources and
++parsers, place discovery, routes and stops, trips/shares, the pages, accent contrast, account data,
++agent twins and discovery artifacts.
++
++## Conventions and pitfalls
++
++- CommonJS, 4-space indent, single quotes, semicolons; problem+json with a stable code. No linter.
++- **Every fact carries its source and fetched time** (city, place, route, stop, photo licence). A
++ missing or failed source is shown honestly; never invent a place, a date, an opening time or a route.
++- **No affiliate and no payment.** Flights are a link to the official search; v1 had no flight API key.
++ The Duffel/Kiwi seam stays off, and a key alone does not turn it on.
++- **TTS is off** until AI has a speech route: every stop says so instead of shipping a dead player.
++- **Attribution is part of the data:** copying a photo to Media or summarising with AI keeps the
++ original source and licence on the place page.
++- **Routes are generated and marked generated**, every stop cited; the no-JavaScript version is a
++ complete SVG + ordered list, not an empty map.
++- **Respect the open-data terms:** no bulk scraping past Overpass/Wikivoyage/Wikidata/Commons limits;
++ use the cached row while a source is unavailable.
++- New env names go into `.env.example` and the README; secret values live only in
++ `/etc/openvibe/tours.env`.
ddiff --git a/CLAUDE.md b/CLAUDE.md
nnew file mode 100644
iindex 0000000..43c994c
---- /dev/null
++++ b/CLAUDE.md
@@@ -0,0 +1 @@