openvibers/openvibe.domains AGENTS.md

AGENTS.md

main 9.4 KB · 159 lines · markdown

1# AGENTS.md — OpenVibe.Domains
2
3## Project overview
4
5"Name it, price it, run it." Domains is two things: a **stateless read-only front** over OpenVibe.Host's
6price catalog and OpenVibe.Tools' public registry data (availability, registrar prices with 1/3/5-year
7totals, per-ending pages, a domain's public registration history), and a **v2 control panel** where a
8person connects a registrar/DNS account and every change is a reviewable proposal. Node.js/Express,
9PostgreSQL for the panel, server-rendered, CommonJS, **no build step**.
10
11- Service id `domains`, port `5070`, origin `https://openvibe.domains`, unit `openvibe-domains.service`,
12 release root `/opt/openvibe.domains`.
13- v1 has **no database, no sign-in, no session, no event stream**; it stores nothing and writes nothing.
14 v2 adds the panel's own `ov_domains` state and Network project secrets for credentials.
15
16## What it owns
17
18- Pages `/`, `/search?q=`, `/tld/:tld`, `/.<ending>` (301 to `/tld/<ending>`), `/d/:domain`, 404 — all
19 server-rendered, readable without JavaScript, in the OpenVibe Frame.
20- The price arithmetic: `y1 = register`, `y3 = register + 2 × renew`, `y5 = five_year` or
21 `register + 4 × renew`; `trap` when `renew ≥ 2 × register` **and** `renew − register ≥ 5`; a total
22 needing a missing price is `null`, never `NaN`; only USD rows are compared/sorted, other currencies
23 follow with `currency_other: true`.
24- The panel (`/panel`): connections (Cloudflare API token with Edit zone DNS + Zone Read, or a Porkbun
25 key pair), zones synced into `domains_zones`, records read live, proposals and their application.
26- `domains.proposal@1`: create/update/delete records, why, what could break, a 7-day decision window; a
27 registrar change is an `NS` list update or a `DNSSEC DS` create/delete. Approve on `/panel/p/:id` or in
28 the Action Center; changes apply in order; a record change applies through the provider API.
29- The JSON API under `/api/v1` (read-only in v1; the panel's routes in v2), discovery files, health/ready.
30- `ov_domains` migrations (`0001_panel.sql`, `0002_config.sql`).
31
32Not owned: the prices and registry data (Host/Tools), the domains themselves and their DNS truth (the
33registrar), credentials (a person's Network project secret, ADR-056), identity (Network).
34
35## Who calls it, and whom it calls
36
37- Callers: people in the browser; the Action Center (proposal approvals); services with a Network service
38 token audience `openvibe.domains` and the route's capability.
39- Depends on:
40 - **OpenVibe.Host** — its internal API (`OV_HOST_INTERNAL_URL`) for the price catalog/check rows.
41 - **OpenVibe.Tools** — its internal API (`OV_TOOLS_INTERNAL_URL`) for "most looked up", "expiring soon"
42 and a domain's public history.
43 - **OpenVibe.Network** — SSO client `domains`, JWKS, and the person's project secret store; a provider
44 credential is stored as a **secret in the person's own Network project** (`OpenVibe.Domains`,
45 ADR-056), granted to `domains` for calls to that provider only
46 (`https://api.cloudflare.com` or `https://api.porkbun.com`), revealed per call (`network.secret.use`)
47 and never kept here. Porkbun's key pair is one JSON secret, parsed only inside its client.
48 - **Events** — account export/deletion; the Action Center's proposal flow.
49 - PostgreSQL (`DATABASE_URL`, `DATABASE_DIRECT_URL` for migrations) and the pinned SDK/Shared.
50
51The two data clients are cached and bounded (`DOMAINS_UPSTREAM_TIMEOUT_MS`, default 8 s); a Host/Tools
52failure shows what is available, never a fabricated price.
53
54## Repository map
55
56- **Entry:** [server/index.js](server/index.js) — `createApp()`, listen, graceful stop.
57- **App:** [server/app.js](server/app.js) — `/`/`/search`/`/tld/:tld`/`/d/:domain`, `/panel/*`, `/auth`,
58 `/api/v1`, `/internal/events`, `/internal/config`, `/shared`, `/css`, discovery, health/ready/release.
59- **Config/db:** [server/config.js](server/config.js), [server/db.js](server/db.js),
60 [server/settings.js](server/settings.js).
61- **Data/pricing:** [server/data/host.js](server/data/host.js), [server/data/tools.js](server/data/tools.js),
62 [server/data/cache.js](server/data/cache.js), [server/pricing.js](server/pricing.js),
63 [server/names.js](server/names.js).
64- **Panel:** [server/panel/panel.js](server/panel/panel.js),
65 [server/panel/vault.js](server/panel/vault.js), [server/panel/cloudflare.js](server/panel/cloudflare.js),
66 [server/panel/porkbun.js](server/panel/porkbun.js).
67- **HTTP/views:** [server/http/api.js](server/http/api.js), `pages.js`, `panel.js`, `principal.js`,
68 `discovery.js`, `router.js`; [server/views/](server/views/) (`index.js`, `panel.js`, `short.js`).
69- **Auth:** [server/auth/sso.js](server/auth/sso.js), [server/auth/keys.js](server/auth/keys.js).
70- **Deploy:** [deploy/systemd/openvibe-domains.service](deploy/systemd/openvibe-domains.service),
71 [deploy/nginx/openvibe.domains.conf](deploy/nginx/openvibe.domains.conf).
72
73## Commands
74
75```bash
76npm install
77npm run dev # http://localhost:5070
78npm start # production
79npm test # test/run.js, each file in its own process, stub upstreams
80npm test -- data # filter by file name; --strict makes a skip fail
81ov test # the harness's sharded run of what the change reaches
82node --check <file.js>
83```
84
85The tests need no database, no network and no running OpenVibe site.
86
87## Database and migrations
88
89The v1 pages need no database. The v2 panel uses `ov_domains` on the host's data role (ADR-035):
90`domains_zones` and the proposal/config state from [migrations/](migrations/). Async through
91`openvibe-sdk/db`; never edit an applied migration, add a new number. **A provider credential is never a
92row here** — only its Network project/secret name; the value is revealed per call.
93
94## Auth and panel security
95
96- v1 pages are public and anonymous; a read is the same for every caller and no session is created.
97- The panel signs a person in through Network SSO (client `domains`, PKCE); the person's Network token is
98 used for their project secrets.
99- A provider token/key is verified before storage, stored in the **person's own Network project** with a
100 grant limited to that provider's origin, and revealed only when a call is made. Nothing is logged or
101 echoed; Porkbun's JSON pair is parsed only in its client.
102- Every DNS change is a proposal with a reason, a risk note and a 7-day window; apply in order, and record
103 the outcome. A registrar change (`NS`/`DS`) uses the registrar API.
104- Service callers need the route's capability (`domains.*`); account export/deletion goes through
105 `/internal/events` only.
106
107## Frontend loading model
108
109All pages are server-rendered and readable without JavaScript; the Frame comes from `openvibe-shared/shell`
110(theme loader, navbar into `#navbar-mount`, shared footer), and `/shared/*` is served from the pinned
111node_modules. There is no SPA, bundler or transpiler. `/css/domains.css` is the only page CSS served here.
112
113## Deploy
114
115Unit [deploy/systemd/openvibe-domains.service](deploy/systemd/openvibe-domains.service) runs
116`/opt/openvibe.domains/server/index.js` as `ubuntu` on port `5070`, env `/etc/openvibe/domains.env`; vhost
117[deploy/nginx/openvibe.domains.conf](deploy/nginx/openvibe.domains.conf) (TLS, `www`→apex 301, GET-only
118`/api/v1`, loopback-only metrics). The repository does not document an `ovhost deploy domains` command —
119use the host's own service installer. CI runs syntax/contracts/security and has no database service.
120
121## Tests
122
123`npm test` runs [test/run.js](test/run.js) with stub upstreams: [test/data.test.js](test/data.test.js)
124(pricing arithmetic, missing price → `null`, currency handling, renew-trap), `test/home-short.test.js`,
125`test/agent-layer.test.js`, `test/account-data.test.js` and helpers. No network or database is needed.
126`ov test` runs the same.
127
128## Conventions
129
130- CommonJS, 4-space indent, single quotes, semicolons; data clients and panel providers are injectable
131 factories so tests pass stubs.
132- Money is integer minor units / rounded cents with a currency; a total that needs a missing input is
133 `null`, not `0` or `NaN`.
134- Prices are labelled with their check date (`checked_at`) and source; "as of" is part of the page.
135- Errors are small JSON problems or the 404 page; a provider/upstream failure is a clear notice, not a 500.
136
137## Pitfalls
138
139- **Never fabricate a price or a total.** Missing input → `null`; only USD rows are compared; other
140 currencies are kept and flagged, never silently converted.
141- **The renew trap is a warning, not an exclusion**: `renew ≥ 2 × register` and `renew − register ≥ 5`;
142 do not reorder the list by it.
143- **Credentials belong to the person's Network project**, never to `ov_domains`, an env file or a log; a
144 provider grant is limited to that provider's origin and revealed per call.
145- **Every DNS change is a proposal** with a reason, risk, 7-day window and ordered application; do not add
146 a direct "apply now" admin path.
147- **`/.<ending>` must 301** to `/tld/<ending>`; a new ending route keeps that alias.
148- **v1 stays read-only**; do not add a write API to the price pages.
149- **The `www` host canonicalises to the apex**; keep the 301.
150- **Loopback-only metrics and GET-only `/api/v1`** in the vhost; do not widen it for a new route without
151 the nginx test.
152
153## Documentation
154
155- [README.md](README.md) — the pages, panel, API, what "priced" means, the data it reads, configuration,
156 run, machine endpoints, deploy.
157- [STATUS.json](STATUS.json) — the v1/v2 state.
158- OpenVibe.Contracts holds `domains.*`/`domains.proposal@1`; Host/Tools/Network own their upstream shapes.
159