openvibers/openvibe.shoes Commits
AGENTS.md: the context an agent starts from (#140)
Changes
nnew file mode 100644@@@ -0,0 +1,100 @@++# AGENTS.md — OpenVibe.Shoes++++## Project overview++++OpenVibe.Shoes is the network's shoe resource at `https://open.shoes`: one page per model with every++store's all-in offer, Shoes' own daily price history and size-price curve, cited specs/fit/care notes,++and an official drops calendar. A person can watch a price or paste a listing link, which Shoes hands++to OpenVibe.Watch.++++- **Entry:** `server/index.js` (Express); app wiring in `server/http/router.js`; port `5180`,++ service id `shoes`, env prefix `SHOES`.++- **Pages:** `server/http/pages.js` (server-rendered HTML via `server/render/layout.js`/`html.js`,++ `server/views/index.js`); own CSS in `public/css/`. No SPA and no build step.++- **API:** `server/http/api.js` (`/api/v1`), OpenAPI in `server/http/openapi.js`, discovery in++ `server/http/discovery.js` (robots, sitemap, llms.txt), SSO in `server/auth/`.++- **Domain:** `server/domain/parse.js` (pure source parsers), `server/domain/drops.js` (release++ calendar); jobs in `server/jobs/` (`offers.js`, `drops.js`, `ebay.js`).++- **Identity:** `server/identity/` and `server/auth/` for Network sessions; the person's own token is++ forwarded to Watch and Today.++++## Owns, does not own++++Owns the shoe catalogue, offers and daily snapshots, drops, watches stored on Shoes, cited facts and++job-run records (`shoe_models`, `shoe_offers`, `shoe_snapshots`, `shoe_drops`, `shoe_watches`,++`shoe_facts`, `shoe_job_runs`, `account_data_events`, `telemetry_samples`). It publishes a search++index document and its share of account export/deletion.++++Does not own: store catalogues or prices (Deals and the official APIs are the sources), a person's++general watch list (OpenVibe.Watch — Shoes forwards the link and keeps only its own `shw_` row), the++calendar (OpenVibe.Today), or any checkout. **No raffle, draw, reservation or checkout is automated,++ever; no affiliate links, no sponsored ordering and no Marketplace scraping.**++++## Who calls it, what it calls++++Public pages and `GET /api/v1/*` need no token; a signed-in person's writes (watch, remind) use their++Network session. Shoes calls OpenVibe.Deals' public offer/product APIs, the eBay Browse API only++when a developer key pair is configured, the official release calendar/rSS feeds, and the person's++own Network token to call OpenVibe.Watch (`shw_` ↔ remote watch) and OpenVibe.Today (drop++reminders). A missing source key turns that source off with a plain note, never an error.++++## Database and migrations++++PostgreSQL through `openvibe-sdk/db`; migrations `migrations/NNNN_*.sql` (0001 initial, 0002 search++index, 0003 shares, 0004 outbox, 0005 product catalog), applied at boot with the owner role. Tests use++PGlite; `npm run test:pg` runs the same suite through PgBouncer. Never edit a migration that has run;++a schema removal waits for a contract migration. Each job writes one `shoe_job_runs` row (counts and++errors) and refuses to start under `SHOES_DRILL=1`; tests call `runOnce()`.++++## Frontend loading model++++Every page is server-rendered HTML with the shared Frame (`openvibe-shared/shell`, navbar/footer,++`/shared/*`); page-specific CSS is in `public/css`. There is no bundler and no route-feature loader.++Interactive bits (watch form, reminder, the small price chart) are inline server output plus small++scripts; keep pages usable without JavaScript and phone-first.++++## Auth++++Network SSO (RS256 JWT + session cookie) using the `shoes` OAuth client; public reads are anonymous.++A person's Watch/Today call uses their own Network token — never a service token on their behalf. A++session without a usable token can still save the watch on Shoes, marked "not connected". Forms are++origin-checked; `/internal/events` is HMAC-verified and loopback-only.++++## Deploy++++Install `deploy/systemd/openvibe-shoes.service` and `deploy/nginx/open.shoes.conf`; the unit reads++`/etc/openvibe/shoes.env`, works in `/opt/openvibe.shoes`, runs as `ubuntu:ubuntu` on++`127.0.0.1:5180`. The vhost proxies `/api/`, `/auth/` and the forms with their own limits.++`sudo ovhost deploy shoes` is the production path (git-checkout, install on lockfile change,++restart, wait for `/api/ready`); rollback with `sudo ovhost rollback shoes --to <sha>`.++++## Commands and tests++++```bash++npm ci --include=dev++npm start # http://127.0.0.1:5180++npm test # every test/*.test.js, PGlite, mock Network++npm test -- importer # only matching files++npm run test:pg # the same through PgBouncer++npm run seed # one run of every job against live sources (needs the network)++```++++`test/run.js` uses `openvibe-shared/test-runner` (90 s, serial); files get a temp PGlite database and++an in-process Network mock, never the network. Tests cover the parsers and all-in price, snapshots++(first check per offer per day wins), the jobs' stop/error behavior, the API and pages, Watch/Today++forwarding, account export/deletion and the shared discovery artifacts. `VERBOSE=1` shows each file's++output.++++## Conventions and pitfalls++++- CommonJS, 4-space indent, single quotes, semicolons; no linter. Paths and domain logic stay in++ `server/domain/` so a parser is testable without HTTP.++- **Prices are all-in** (price + stated shipping); keep currency and the source's own check time, and++ mark a stale offer rather than hiding it.++- **The first snapshot of an offer per day wins** (unique key); do not overwrite history.++- **Cite or say "not known yet":** never invent a spec, fit note or release date; a parser that++ cannot read a date leaves it out.++- **Respect the source rules:** Deals public pages, eBay's official API only with keys, official++ release pages; no scraping of Marketplace or Facebook, no affiliate URLs.++- New environment variables go in `.env.example` and the README list; secrets live only in++ `/etc/openvibe/shoes.env`.nnew file mode 100644@@@ -0,0 +1 @@
new file mode 100644
new file mode 100644
@@ -0,0 +1,100 @@
@@ -0,0 +1,100 @@
+# AGENTS.md — OpenVibe.Shoes
+
+## Project overview
+
+OpenVibe.Shoes is the network's shoe resource at `https://open.shoes`: one page per model with every
+store's all-in offer, Shoes' own daily price history and size-price curve, cited specs/fit/care notes,
+and an official drops calendar. A person can watch a price or paste a listing link, which Shoes hands
+to OpenVibe.Watch.
+
+- **Entry:** `server/index.js` (Express); app wiring in `server/http/router.js`; port `5180`,
+ service id `shoes`, env prefix `SHOES`.
+- **Pages:** `server/http/pages.js` (server-rendered HTML via `server/render/layout.js`/`html.js`,
+ `server/views/index.js`); own CSS in `public/css/`. No SPA and no build step.
+- **API:** `server/http/api.js` (`/api/v1`), OpenAPI in `server/http/openapi.js`, discovery in
+ `server/http/discovery.js` (robots, sitemap, llms.txt), SSO in `server/auth/`.
+- **Domain:** `server/domain/parse.js` (pure source parsers), `server/domain/drops.js` (release
+ calendar); jobs in `server/jobs/` (`offers.js`, `drops.js`, `ebay.js`).
+- **Identity:** `server/identity/` and `server/auth/` for Network sessions; the person's own token is
+ forwarded to Watch and Today.
+
+## Owns, does not own
+
+Owns the shoe catalogue, offers and daily snapshots, drops, watches stored on Shoes, cited facts and
+job-run records (`shoe_models`, `shoe_offers`, `shoe_snapshots`, `shoe_drops`, `shoe_watches`,
+`shoe_facts`, `shoe_job_runs`, `account_data_events`, `telemetry_samples`). It publishes a search
+index document and its share of account export/deletion.
+
+Does not own: store catalogues or prices (Deals and the official APIs are the sources), a person's
+general watch list (OpenVibe.Watch — Shoes forwards the link and keeps only its own `shw_` row), the
+calendar (OpenVibe.Today), or any checkout. **No raffle, draw, reservation or checkout is automated,
+ever; no affiliate links, no sponsored ordering and no Marketplace scraping.**
+
+## Who calls it, what it calls
+
+Public pages and `GET /api/v1/*` need no token; a signed-in person's writes (watch, remind) use their
+Network session. Shoes calls OpenVibe.Deals' public offer/product APIs, the eBay Browse API only
+when a developer key pair is configured, the official release calendar/rSS feeds, and the person's
+own Network token to call OpenVibe.Watch (`shw_` ↔ remote watch) and OpenVibe.Today (drop
+reminders). A missing source key turns that source off with a plain note, never an error.
+
+## Database and migrations
+
+PostgreSQL through `openvibe-sdk/db`; migrations `migrations/NNNN_*.sql` (0001 initial, 0002 search
+index, 0003 shares, 0004 outbox, 0005 product catalog), applied at boot with the owner role. Tests use
+PGlite; `npm run test:pg` runs the same suite through PgBouncer. Never edit a migration that has run;
+a schema removal waits for a contract migration. Each job writes one `shoe_job_runs` row (counts and
+errors) and refuses to start under `SHOES_DRILL=1`; tests call `runOnce()`.
+
+## Frontend loading model
+
+Every page is server-rendered HTML with the shared Frame (`openvibe-shared/shell`, navbar/footer,
+`/shared/*`); page-specific CSS is in `public/css`. There is no bundler and no route-feature loader.
+Interactive bits (watch form, reminder, the small price chart) are inline server output plus small
+scripts; keep pages usable without JavaScript and phone-first.
+
+## Auth
+
+Network SSO (RS256 JWT + session cookie) using the `shoes` OAuth client; public reads are anonymous.
+A person's Watch/Today call uses their own Network token — never a service token on their behalf. A
+session without a usable token can still save the watch on Shoes, marked "not connected". Forms are
+origin-checked; `/internal/events` is HMAC-verified and loopback-only.
+
+## Deploy
+
+Install `deploy/systemd/openvibe-shoes.service` and `deploy/nginx/open.shoes.conf`; the unit reads
+`/etc/openvibe/shoes.env`, works in `/opt/openvibe.shoes`, runs as `ubuntu:ubuntu` on
+`127.0.0.1:5180`. The vhost proxies `/api/`, `/auth/` and the forms with their own limits.
+`sudo ovhost deploy shoes` is the production path (git-checkout, install on lockfile change,
+restart, wait for `/api/ready`); rollback with `sudo ovhost rollback shoes --to <sha>`.
+
+## Commands and tests
+
+```bash
+npm ci --include=dev
+npm start # http://127.0.0.1:5180
+npm test # every test/*.test.js, PGlite, mock Network
+npm test -- importer # only matching files
+npm run test:pg # the same through PgBouncer
+npm run seed # one run of every job against live sources (needs the network)
+```
+
+`test/run.js` uses `openvibe-shared/test-runner` (90 s, serial); files get a temp PGlite database and
+an in-process Network mock, never the network. Tests cover the parsers and all-in price, snapshots
+(first check per offer per day wins), the jobs' stop/error behavior, the API and pages, Watch/Today
+forwarding, account export/deletion and the shared discovery artifacts. `VERBOSE=1` shows each file's
+output.
+
+## Conventions and pitfalls
+
+- CommonJS, 4-space indent, single quotes, semicolons; no linter. Paths and domain logic stay in
+ `server/domain/` so a parser is testable without HTTP.
+- **Prices are all-in** (price + stated shipping); keep currency and the source's own check time, and
+ mark a stale offer rather than hiding it.
+- **The first snapshot of an offer per day wins** (unique key); do not overwrite history.
+- **Cite or say "not known yet":** never invent a spec, fit note or release date; a parser that
+ cannot read a date leaves it out.
+- **Respect the source rules:** Deals public pages, eBay's official API only with keys, official
+ release pages; no scraping of Marketplace or Facebook, no affiliate URLs.
+- New environment variables go in `.env.example` and the README list; secrets live only in
+ `/etc/openvibe/shoes.env`.
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@