openvibers/openvibe.vip AGENTS.md

AGENTS.md

main 6.5 KB · 105 lines · markdown

1# AGENTS.md — OpenVibe.VIP
2
3## Project overview
4
5OpenVibe.VIP (`https://openvibe.vip`) is membership on the OpenVibe network: creator and network plans
6with versioned terms, perks, gated-resource policy, members-only posts, and a product-wide answer to
7"is this person a member, and may they see this?". VIP owns plans and perks; **Billing owns
8subscriptions, prices and the entitlement truth** (ADR-012).
9
10- **Entry:** `server/index.js`; app `server/app.js`; port `4620`, service id `vip`.
11- **API:** `server/api/v1.js`, auth `server/api/auth.js`, limits `server/api/actor-limits.js`.
12- **Domain:** `server/domain/` — `plans.js` (versions), `perks.js`, `policies.js` (gated resources),
13 `memberships.js`, `entitlements.js` (projection), `periods.js`, `checkout.js`, `posts.js`,
14 `prices.js`, `creators.js`, `cards.js`, `markdown.js`, `account-data.js`, `index.js`.
15- **Pages and embeds:** `server/web/` (`pages.js`, `routes.js`, `layout.js`, `session.js`,
16 `embeds.js`, `forms.js`, `short.js`); the public card/badge/embed at `/embed`; `public/js/vip.js`
17 and `post-upload.js`; own CSS in `public/css`.
18- **Consumer client:** `client/vip-client.js` — the package products use to check membership and gate
19 a resource, with the product cache and its convergence bound (README "Consumer seam").
20- **Docs:** `docs/cutover-0002-renewal-periods.md` (renewal periods/grace), `SECURITY.md`.
21
22## Owns, does not own
23
24VIP is the only writer of the nine authority tables: `vip_creators`, `vip_plans`,
25`vip_plan_versions` (immutable; triggers refuse UPDATE/DELETE except the publish stamp), `vip_perks`,
26`vip_plan_perks` (immutable per version), `vip_product_bindings`, `vip_member_preferences`,
27`vip_gated_resource_rules` and `vip_migration_maps`. Operational tables: `vip_memberships` (which plan
28version a membership was bought under — never whether it is active), `vip_entitlement_projection` (a
29cache with `valid_until`), `vip_checkouts`, `vip_membership_periods` (Billing's charge records),
30`event_outbox` and `idempotency_receipts`.
31
32Does not own: subscription contracts, prices, charges, renewals, cancellations, refunds, chargebacks or
33canonical entitlements (Billing — VIP never prices anything and never moves money; plan terms carry no
34price); identities (Network); products' own resources (only an EntityRef); badge rendering (each
35product).
36
37## Who calls it, what it calls
38
39People use the pages/API with a Network user JWT and act on their own plans, perks, rules, members,
40memberships and checks; staff with `staff.site.configure` manage network plans. Services use
41client-credentials tokens for audience `openvibe.vip` with one `vip.*` capability per route. Products
42consume VIP through `client/vip-client.js` / SDK `openvibe-sdk/vip` and the product cache: Chat (member
43badge), Community (members-only spaces/threads), Blog (members-only posts), Wiki (VIP spaces/pages);
44Live is not wired. VIP calls Billing (`billing.intent.create`, `billing.subscription.manage`,
45`billing.entitlement.check`), Network (JWKS, OAuth), Events (Billing deliveries in, VIP outbox out) and
46Media (a members-only post's image, `media.object.upload`, `vip` tenant; without it the post has none).
47
48## Database and migrations
49
50PostgreSQL `ov_vip` through `openvibe-sdk/db` (PgBouncer for the app, direct owner role for
51migrations); Valkey optionally holds per-actor limit counters. Migrations `0001..0007` include plan
52tiers, account data, telemetry, member posts and the outbox rejection index; applied at boot, never
53edited after they run, only additive so a rollback works. The immutability of plan versions and
54plan-perks is enforced by database triggers, not only by code.
55
56## Entitlements and auth
57
58Every API route checks one capability (service) or ownership (person); the API never reads cookies.
59Billing events arrive HMAC-SHA256-signed over the raw body (`VIP_EVENTS_SECRET`, rotation by list),
60`source` must be `billing`, the inbox is exactly-once and the ordering guard rejects a stale period.
61**Fail closed everywhere a membership is decided:** "unknown" is never "yes"; the projection has a
62`valid_until` and a convergence bound, and an expired/unsynced row is not a membership.
63
64## Deploy
65
66`sudo ovhost deploy vip` (git-checkout: fast-forward `/opt/openvibe.vip`, install on a lockfile
67change, restart, wait for `/api/ready`; rollback `sudo ovhost rollback vip --to <sha>`). Unit
68`openvibe-vip.service` on `127.0.0.1:4620`, env `/etc/openvibe/vip.env`, database `ov_vip`, nginx
69`deploy/nginx/openvibe.vip.conf` (live since 2026-10-17). The release migrates at boot.
70
71## Commands and tests
72
73```bash
74npm ci
75npm run dev # http://127.0.0.1:4620
76npm test # every test/*.test.js, in-process Network/Billing stubs
77npm test -- plans import # only matching files
78npm run test:pg # the same on PostgreSQL
79npm run subscribe # create the Events subscriptions once
80```
81
82`test/run.js` uses `openvibe-shared/test-runner` (60 s, serial); temp databases and stubs, never the
83network. Tests cover immutable plan versions, perks/bindings, gated policy incl. sensitive resources,
84the Billing projection and convergence, renewal periods/grace/refunds, checkout/cancel, member posts,
85the consumer client/cache, the embed/card, account export/deletion (a paid period stays), and the
86pages.
87
88## Conventions and pitfalls
89
90- CommonJS, 4-space indent, single quotes, semicolons; RFC 9457 problem+json; escaped HTML and a safe
91 Markdown renderer (a post source can never carry markup).
92- **Never store "is a member".** `vip_memberships` says which version was bought; access is answered
93 by the entitlement check/projection, and a projection row is a cache with a validated `valid_until`.
94- **Never put a price in plan terms.** The terms say Billing sets and charges it; `vip_prices` is a
95 display of Billing's numbers, not an authority.
96- **Immutability is real:** do not add an UPDATE path to `vip_plan_versions`/`vip_plan_perks`; publish
97 stamps once, then a new version is a new row.
98- **A gated resource rule is policy, not content:** add a `{service, type, id}` EntityRef rule and a
99 product binding; the product still renders its own resource.
100- **Billing is exactly-once and ordered:** a redelivery, a stale `billing.entitlement.changed` or a
101 reversed transaction must converge, not double-count a period or grant access after a refund.
102- **Deleting a member does not delete the books:** memberships/preferences/projection/checkouts go,
103 paid periods stay; a deleted creator is suspended without their name but plan versions members joined
104 under stay whole.
105