openvibers/openvibe.trade AGENTS.md
AGENTS.md
| 1 | # AGENTS.md — OpenVibe.Trade |
| 2 | |
| 3 | ## Project overview |
| 4 | |
| 5 | OpenVibe.Trade (`https://openvibe.trade`) is an **informational** market context product: instruments |
| 6 | and aliases with deterministic resolution, timestamped market observations with their sources, SEC |
| 7 | filings and other source documents, private watchlists and alerts, short AI briefs from filed numbers, |
| 8 | and editor-written/edited context. It is explicitly **not** custody, order execution, a marketplace or |
| 9 | investment advice (ADR-025); the disclaimer is on every page. |
| 10 | |
| 11 | - **Entry:** `server/index.js`; app `server/app.js`; port `4860`, service id `trade`. |
| 12 | - **API:** `server/http/api.js` (`/api/v1`), `api`/`editor`/`private` route groups, `discovery.js`, |
| 13 | `actor-limits.js`; pages `server/http/pages.js` and `server/render/` (`home.js`, `layout.js`, |
| 14 | `views.js`, `short.js`); `public/js/passkey-signin.js` is the only own script, CSS in `public/css`. |
| 15 | - **Domain:** `server/domain/` — `instruments.js`, `observations.js`, `documents.js`, `facts.js`, |
| 16 | `mapping.js`, `context.js`, `briefs.js`/`market-briefs.js`/`ai-briefs`, `alerts.js`, `watchlists.js`, |
| 17 | `ingest.js`, `reading.js`, `source-status.js`, `account-data.js`, `indexing.js`, `urls.js`. |
| 18 | - **Clients and events:** `server/clients/sec.js` (SEC ticker/XBRL data), `server/clients/ai.js`, |
| 19 | `server/events/consumer.js` (Sources items, account export/deletion), SDK outbox. |
| 20 | - **Docs:** `docs/marketplace-open-question.md` (the historical marketplace branch); `SECURITY.md` has |
| 21 | the threat review. |
| 22 | |
| 23 | ## Owns, does not own |
| 24 | |
| 25 | Owns its nine charter tables in `ov_trade`: `trade_instruments` (symbol, name, kind, exchange, SEC |
| 26 | CIK, currency, status, published context revision), `trade_instrument_aliases` (tickers/CIKs/names |
| 27 | with a normalised resolution key), `trade_watchlists` (private, one owner), the alert rules/deliveries, |
| 28 | observations, documents, context revisions, briefs, reviews, shares and outbox. |
| 29 | |
| 30 | Does not own: money, custody, orders, escrow, listings or advice (none exist here; money is Billing's, |
| 31 | ADR-012/025); source items (Sources owns ingestion, robots, terms, items and their removals — Trade |
| 32 | keeps metadata keyed by item id and follows revisions/removals); identity (Network); notifications |
| 33 | (Network renders/delivers them from `trade.alert.triggered`; Trade sends no email). |
| 34 | |
| 35 | ## Who calls it, what it calls |
| 36 | |
| 37 | Public reads (`GET /instruments`, `/instruments/:symbol`, observations, documents, `/sources`) are |
| 38 | anonymous and cacheable. People sign in with Network SSO for watchlists/alerts; editors use |
| 39 | instrument and context management; services use audience `openvibe.trade` tokens with |
| 40 | `trade.instrument.resolve`, `trade.instrument.manage`, `trade.observation.write` (services only) and |
| 41 | `trade.context.read`. SDK `openvibe-sdk/trade` is the client surface. Trade calls Network (JWKS, SSO), |
| 42 | Sources (items, revisions, removals; `svc:trade` created the `sec-company-tickers` source), SEC's own |
| 43 | tickers/XBRL, AI (`trade.summarize_market_context`, labelled output) and Events (outbox: |
| 44 | `trade.alert.triggered`, context/brief events). |
| 45 | |
| 46 | ## Database and migrations |
| 47 | |
| 48 | PostgreSQL `ov_trade` through `openvibe-sdk/db` (PgBouncer for the app, direct owner role for |
| 49 | migrations); migrations `0001..0010` cover instruments, observations, documents, alerts, AI/market |
| 50 | briefs, shares, the brief batch and the outbox rejection index. Applied at boot; only add tables and |
| 51 | columns — never edit one that has run, and a rollback must keep working. Tests use PGlite or, with |
| 52 | `TRADE_TEST_STORE=pg`, the PostgreSQL containers. |
| 53 | |
| 54 | ## Auth and privacy |
| 55 | |
| 56 | Only verified Network RS256 JWTs (offline against JWKS) and service tokens for audience |
| 57 | `openvibe.trade`. A bad service token is refused, never downgraded to anonymous; identity never comes |
| 58 | from a body or query, and `X-OV-*` headers are ignored for browsers. Watchlists and alerts are the |
| 59 | person's own; a share is read-only. Public pages are cacheable with no personal data; a person's page |
| 60 | is `private, no-store`. `/internal/events` is signed (v2) and loopback-only; account export/deletion |
| 61 | covers every table with a person column, leaving contributions without the person id. |
| 62 | |
| 63 | ## Deploy |
| 64 | |
| 65 | `sudo ovhost deploy trade` (git-checkout: fetch, fast-forward `/opt/openvibe.trade`, install on a |
| 66 | lockfile change, restart, wait for `/api/ready`; rollback `sudo ovhost rollback trade --to <sha>`). |
| 67 | Unit `openvibe-trade.service` on `127.0.0.1:4860`, env `/etc/openvibe/trade.env`, database `ov_trade`; |
| 68 | the release migrates at boot. |
| 69 | |
| 70 | ## Commands and tests |
| 71 | |
| 72 | ```bash |
| 73 | npm ci |
| 74 | npm run dev # http://127.0.0.1:4860 |
| 75 | npm test # every test/*.test.js, PGlite + mock Network/Sources |
| 76 | npm test -- schedule # only matching files |
| 77 | npm run test:pg # the same on PostgreSQL |
| 78 | ``` |
| 79 | |
| 80 | `test/run.js` uses `openvibe-shared/test-runner` (60 s, serial); PGlite plus in-process Network and |
| 81 | Sources mocks, never the network. Coverage includes instrument/alias resolution, observations and |
| 82 | freshness, alerts and their scheduling, filings/documents and citations, AI/context briefs, watchlists |
| 83 | and shares, the route inventory/scope test, capabilities/contracts, CSP/analytics, account data and |
| 84 | the pages. |
| 85 | |
| 86 | ## Conventions and pitfalls |
| 87 | |
| 88 | - CommonJS, 4-space indent, single quotes, semicolons; problem+json errors and escaped HTML. |
| 89 | - **Information only:** no route may hold or move value, execute an order, escrow, list a marketplace |
| 90 | item, or read as advice. The route-inventory test and ADR-025 enforce the scope; a new "trade" |
| 91 | feature that touches money belongs in Billing, not here. |
| 92 | - **Every observation shows when it was observed, when it was retrieved and which source stated it.** |
| 93 | A missing value is null/"not known", never zero or inferred. |
| 94 | - **Resolution is deterministic:** a ticker or CIK names exactly one instrument (unique), a name may be |
| 95 | ambiguous and must return candidates; never pick one silently. |
| 96 | - **AI briefs are labelled and cite the filed numbers/documents**; they are not reviewed by a person. |
| 97 | Editor context is reviewed; drafts are only visible with `?all=1` to an editor and the AI workflow |
| 98 | input route is capability-guarded. |
| 99 | - **Sources revisions and removals win:** when Sources retracts an item, Trade's copy follows and a |
| 100 | citation to it must not survive as if still sourced. |
| 101 | - **Passkeys/sign-in never expose the token** to the page: the session is a Network cookie (or Bearer |
| 102 | for the API); `public/js/passkey-signin.js` only drives the browser ceremony. |
| 103 |