openvibers/openvibe.domains AGENTS.md

AGENTS.md

main 9.4 KB · 159 lines · markdown

AGENTS.md — OpenVibe.Domains

Project overview

"Name it, price it, run it." Domains is two things: a stateless read-only front over OpenVibe.Host's
price catalog and OpenVibe.Tools' public registry data (availability, registrar prices with 1/3/5-year
totals, per-ending pages, a domain's public registration history), and a v2 control panel where a
person connects a registrar/DNS account and every change is a reviewable proposal. Node.js/Express,
PostgreSQL for the panel, server-rendered, CommonJS, no build step.

  • Service id domains, port 5070, origin https://openvibe.domains, unit openvibe-domains.service,
    release root /opt/openvibe.domains.
  • v1 has no database, no sign-in, no session, no event stream; it stores nothing and writes nothing.
    v2 adds the panel's own ov_domains state and Network project secrets for credentials.

What it owns

  • Pages /, /search?q=, /tld/:tld, /.<ending> (301 to /tld/<ending>), /d/:domain, 404 — all
    server-rendered, readable without JavaScript, in the OpenVibe Frame.
  • The price arithmetic: y1 = register, y3 = register + 2 × renew, y5 = five_year or
    register + 4 × renew; trap when renew ≥ 2 × register and renew − register ≥ 5; a total
    needing a missing price is null, never NaN; only USD rows are compared/sorted, other currencies
    follow with currency_other: true.
  • The panel (/panel): connections (Cloudflare API token with Edit zone DNS + Zone Read, or a Porkbun
    key pair), zones synced into domains_zones, records read live, proposals and their application.
  • domains.proposal@1: create/update/delete records, why, what could break, a 7-day decision window; a
    registrar change is an NS list update or a DNSSEC DS create/delete. Approve on /panel/p/:id or in
    the Action Center; changes apply in order; a record change applies through the provider API.
  • The JSON API under /api/v1 (read-only in v1; the panel's routes in v2), discovery files, health/ready.
  • ov_domains migrations (0001_panel.sql, 0002_config.sql).

Not owned: the prices and registry data (Host/Tools), the domains themselves and their DNS truth (the
registrar), credentials (a person's Network project secret, ADR-056), identity (Network).

Who calls it, and whom it calls

  • Callers: people in the browser; the Action Center (proposal approvals); services with a Network service
    token audience openvibe.domains and the route's capability.
  • Depends on:
  • OpenVibe.Host — its internal API (OV_HOST_INTERNAL_URL) for the price catalog/check rows.
  • OpenVibe.Tools — its internal API (OV_TOOLS_INTERNAL_URL) for "most looked up", "expiring soon"
    and a domain's public history.
  • OpenVibe.Network — SSO client domains, JWKS, and the person's project secret store; a provider
    credential is stored as a secret in the person's own Network project (OpenVibe.Domains,
    ADR-056), granted to domains for calls to that provider only
    (https://api.cloudflare.com or https://api.porkbun.com), revealed per call (network.secret.use)
    and never kept here. Porkbun's key pair is one JSON secret, parsed only inside its client.
  • Events — account export/deletion; the Action Center's proposal flow.
  • PostgreSQL (DATABASE_URL, DATABASE_DIRECT_URL for migrations) and the pinned SDK/Shared.

The two data clients are cached and bounded (DOMAINS_UPSTREAM_TIMEOUT_MS, default 8 s); a Host/Tools
failure shows what is available, never a fabricated price.

Repository map

  • Entry: [server/index.js](server/index.js) — createApp(), listen, graceful stop.
  • App: [server/app.js](server/app.js) — ///search//tld/:tld//d/:domain, /panel/*, /auth,
    /api/v1, /internal/events, /internal/config, /shared, /css, discovery, health/ready/release.
  • Config/db: [server/config.js](server/config.js), [server/db.js](server/db.js),
    [server/settings.js](server/settings.js).
  • Data/pricing: [server/data/host.js](server/data/host.js), [server/data/tools.js](server/data/tools.js),
    [server/data/cache.js](server/data/cache.js), [server/pricing.js](server/pricing.js),
    [server/names.js](server/names.js).
  • Panel: [server/panel/panel.js](server/panel/panel.js),
    [server/panel/vault.js](server/panel/vault.js), [server/panel/cloudflare.js](server/panel/cloudflare.js),
    [server/panel/porkbun.js](server/panel/porkbun.js).
  • HTTP/views: [server/http/api.js](server/http/api.js), pages.js, panel.js, principal.js,
    discovery.js, router.js; [server/views/](server/views/) (index.js, panel.js, short.js).
  • Auth: [server/auth/sso.js](server/auth/sso.js), [server/auth/keys.js](server/auth/keys.js).
  • Deploy: [deploy/systemd/openvibe-domains.service](deploy/systemd/openvibe-domains.service),
    [deploy/nginx/openvibe.domains.conf](deploy/nginx/openvibe.domains.conf).

Commands

npm install
npm run dev                # http://localhost:5070
npm start                  # production
npm test                   # test/run.js, each file in its own process, stub upstreams
npm test -- data           # filter by file name; --strict makes a skip fail
ov test                    # the harness's sharded run of what the change reaches
node --check <file.js>

The tests need no database, no network and no running OpenVibe site.

Database and migrations

The v1 pages need no database. The v2 panel uses ov_domains on the host's data role (ADR-035):
domains_zones and the proposal/config state from [migrations/](migrations/). Async through
openvibe-sdk/db; never edit an applied migration, add a new number. A provider credential is never a
row here
— only its Network project/secret name; the value is revealed per call.

Auth and panel security

  • v1 pages are public and anonymous; a read is the same for every caller and no session is created.
  • The panel signs a person in through Network SSO (client domains, PKCE); the person's Network token is
    used for their project secrets.
  • A provider token/key is verified before storage, stored in the person's own Network project with a
    grant limited to that provider's origin, and revealed only when a call is made. Nothing is logged or
    echoed; Porkbun's JSON pair is parsed only in its client.
  • Every DNS change is a proposal with a reason, a risk note and a 7-day window; apply in order, and record
    the outcome. A registrar change (NS/DS) uses the registrar API.
  • Service callers need the route's capability (domains.*); account export/deletion goes through
    /internal/events only.

Frontend loading model

All pages are server-rendered and readable without JavaScript; the Frame comes from openvibe-shared/shell
(theme loader, navbar into #navbar-mount, shared footer), and /shared/* is served from the pinned
node_modules. There is no SPA, bundler or transpiler. /css/domains.css is the only page CSS served here.

Deploy

Unit [deploy/systemd/openvibe-domains.service](deploy/systemd/openvibe-domains.service) runs
/opt/openvibe.domains/server/index.js as ubuntu on port 5070, env /etc/openvibe/domains.env; vhost
[deploy/nginx/openvibe.domains.conf](deploy/nginx/openvibe.domains.conf) (TLS, www→apex 301, GET-only
/api/v1, loopback-only metrics). The repository does not document an ovhost deploy domains command —
use the host's own service installer. CI runs syntax/contracts/security and has no database service.

Tests

npm test runs [test/run.js](test/run.js) with stub upstreams: [test/data.test.js](test/data.test.js)
(pricing arithmetic, missing price → null, currency handling, renew-trap), test/home-short.test.js,
test/agent-layer.test.js, test/account-data.test.js and helpers. No network or database is needed.
ov test runs the same.

Conventions

  • CommonJS, 4-space indent, single quotes, semicolons; data clients and panel providers are injectable
    factories so tests pass stubs.
  • Money is integer minor units / rounded cents with a currency; a total that needs a missing input is
    null, not 0 or NaN.
  • Prices are labelled with their check date (checked_at) and source; "as of" is part of the page.
  • Errors are small JSON problems or the 404 page; a provider/upstream failure is a clear notice, not a 500.

Pitfalls

  • Never fabricate a price or a total. Missing input → null; only USD rows are compared; other
    currencies are kept and flagged, never silently converted.
  • The renew trap is a warning, not an exclusion: renew ≥ 2 × register and renew − register ≥ 5;
    do not reorder the list by it.
  • Credentials belong to the person's Network project, never to ov_domains, an env file or a log; a
    provider grant is limited to that provider's origin and revealed per call.
  • Every DNS change is a proposal with a reason, risk, 7-day window and ordered application; do not add
    a direct "apply now" admin path.
  • /.<ending> must 301 to /tld/<ending>; a new ending route keeps that alias.
  • v1 stays read-only; do not add a write API to the price pages.
  • The www host canonicalises to the apex; keep the 301.
  • Loopback-only metrics and GET-only /api/v1 in the vhost; do not widen it for a new route without
    the nginx test.

Documentation

  • [README.md](README.md) — the pages, panel, API, what "priced" means, the data it reads, configuration,
    run, machine endpoints, deploy.
  • [STATUS.json](STATUS.json) — the v1/v2 state.
  • OpenVibe.Contracts holds domains.*/domains.proposal@1; Host/Tools/Network own their upstream shapes.