openvibers/openvibe.trade AGENTS.md
AGENTS.md
AGENTS.md — OpenVibe.Trade
Project overview
OpenVibe.Trade (https://openvibe.trade) is an informational market context product: instruments
and aliases with deterministic resolution, timestamped market observations with their sources, SEC
filings and other source documents, private watchlists and alerts, short AI briefs from filed numbers,
and editor-written/edited context. It is explicitly not custody, order execution, a marketplace or
investment advice (ADR-025); the disclaimer is on every page.
- Entry:
server/index.js; appserver/app.js; port4860, service idtrade. - API:
server/http/api.js(/api/v1),api/editor/privateroute groups,discovery.js,
actor-limits.js; pagesserver/http/pages.jsandserver/render/(home.js,layout.js,
views.js,short.js);public/js/passkey-signin.jsis the only own script, CSS inpublic/css. - Domain:
server/domain/—instruments.js,observations.js,documents.js,facts.js,
mapping.js,context.js,briefs.js/market-briefs.js/ai-briefs,alerts.js,watchlists.js,
ingest.js,reading.js,source-status.js,account-data.js,indexing.js,urls.js. - Clients and events:
server/clients/sec.js(SEC ticker/XBRL data),server/clients/ai.js,
server/events/consumer.js(Sources items, account export/deletion), SDK outbox. - Docs:
docs/marketplace-open-question.md(the historical marketplace branch);SECURITY.mdhas
the threat review.
Owns, does not own
Owns its nine charter tables in ov_trade: trade_instruments (symbol, name, kind, exchange, SEC
CIK, currency, status, published context revision), trade_instrument_aliases (tickers/CIKs/names
with a normalised resolution key), trade_watchlists (private, one owner), the alert rules/deliveries,
observations, documents, context revisions, briefs, reviews, shares and outbox.
Does not own: money, custody, orders, escrow, listings or advice (none exist here; money is Billing's,
ADR-012/025); source items (Sources owns ingestion, robots, terms, items and their removals — Trade
keeps metadata keyed by item id and follows revisions/removals); identity (Network); notifications
(Network renders/delivers them from trade.alert.triggered; Trade sends no email).
Who calls it, what it calls
Public reads (GET /instruments, /instruments/:symbol, observations, documents, /sources) are
anonymous and cacheable. People sign in with Network SSO for watchlists/alerts; editors use
instrument and context management; services use audience openvibe.trade tokens with
trade.instrument.resolve, trade.instrument.manage, trade.observation.write (services only) and
trade.context.read. SDK openvibe-sdk/trade is the client surface. Trade calls Network (JWKS, SSO),
Sources (items, revisions, removals; svc:trade created the sec-company-tickers source), SEC's own
tickers/XBRL, AI (trade.summarize_market_context, labelled output) and Events (outbox:
trade.alert.triggered, context/brief events).
Database and migrations
PostgreSQL ov_trade through openvibe-sdk/db (PgBouncer for the app, direct owner role for
migrations); migrations 0001..0010 cover instruments, observations, documents, alerts, AI/market
briefs, shares, the brief batch and the outbox rejection index. Applied at boot; only add tables and
columns — never edit one that has run, and a rollback must keep working. Tests use PGlite or, with
TRADE_TEST_STORE=pg, the PostgreSQL containers.
Auth and privacy
Only verified Network RS256 JWTs (offline against JWKS) and service tokens for audience
openvibe.trade. A bad service token is refused, never downgraded to anonymous; identity never comes
from a body or query, and X-OV-* headers are ignored for browsers. Watchlists and alerts are the
person's own; a share is read-only. Public pages are cacheable with no personal data; a person's page
is private, no-store. /internal/events is signed (v2) and loopback-only; account export/deletion
covers every table with a person column, leaving contributions without the person id.
Deploy
sudo ovhost deploy trade (git-checkout: fetch, fast-forward /opt/openvibe.trade, install on a
lockfile change, restart, wait for /api/ready; rollback sudo ovhost rollback trade --to <sha>).
Unit openvibe-trade.service on 127.0.0.1:4860, env /etc/openvibe/trade.env, database ov_trade;
the release migrates at boot.
Commands and tests
npm ci
npm run dev # http://127.0.0.1:4860
npm test # every test/*.test.js, PGlite + mock Network/Sources
npm test -- schedule # only matching files
npm run test:pg # the same on PostgreSQL
test/run.js uses openvibe-shared/test-runner (60 s, serial); PGlite plus in-process Network and
Sources mocks, never the network. Coverage includes instrument/alias resolution, observations and
freshness, alerts and their scheduling, filings/documents and citations, AI/context briefs, watchlists
and shares, the route inventory/scope test, capabilities/contracts, CSP/analytics, account data and
the pages.
Conventions and pitfalls
- CommonJS, 4-space indent, single quotes, semicolons; problem+json errors and escaped HTML.
- Information only: no route may hold or move value, execute an order, escrow, list a marketplace
item, or read as advice. The route-inventory test and ADR-025 enforce the scope; a new "trade"
feature that touches money belongs in Billing, not here. - Every observation shows when it was observed, when it was retrieved and which source stated it.
A missing value is null/"not known", never zero or inferred. - Resolution is deterministic: a ticker or CIK names exactly one instrument (unique), a name may be
ambiguous and must return candidates; never pick one silently. - AI briefs are labelled and cite the filed numbers/documents; they are not reviewed by a person.
Editor context is reviewed; drafts are only visible with?all=1to an editor and the AI workflow
input route is capability-guarded. - Sources revisions and removals win: when Sources retracts an item, Trade's copy follows and a
citation to it must not survive as if still sourced. - Passkeys/sign-in never expose the token to the page: the session is a Network cookie (or Bearer
for the API);public/js/passkey-signin.jsonly drives the browser ceremony.