openvibers/openvibe.watch AGENTS.md
AGENTS.md
AGENTS.md — OpenVibe.Watch
Project overview
OpenVibe.Watch (https://openvibe.watch) is the network's place for a person or an agent to define
persistent observations and conditions over pages, feeds, APIs, webhooks and OpenVibe events, and an
action when they change or fire — cheaply, because a Watch prefers an event or the site's own ETag
over polling. The API is loopback-only; the public site is server-rendered.
- Entry:
server/index.js; appserver/app.js; port4730, service idwatch. - API:
server/api/—watches.js,checks.js,provider-rates.js,resource-index.js,
resource-control.js,actor-limits.js(all/api/v1);server/http/—pages.js,
discovery.js,site-limits.js,network-reads.js. - Carriers (binding order):
server/carriers/—event.js(OpenVibe.Events),feed.js,api.js,
node.js(Link probes from the owner's machine),http.js,webhook.js; the preference logic is in
index.js. - Checks: scheduler, conditional fetch and backoff in
server/check*/server/net/fetcher.js;
the outbound guard isserver/net/guard.js; checks and observations live incheck_runs/
observationswithwatch_endpoint_state. - Events and actions:
server/events/(envelope.js,agent-step.js), actions in the check path
(notification, event, webhook, Actor task/agent run, Run job, Codes task). - Pages:
server/render/(watch.js,layout.js,html.js,changelog.js,short.js); no own
browser script layer, CSS inpublic/css; every page complete without JavaScript.
Owns, does not own
Owns the watch registry (watches, watch_endpoint_state) and the watch.watch@1 /
watch.watch-request@1 / watch.watch-result@1 shapes; checks (check_runs, observations, the
scheduler, conditional-fetch state, backoff); the public site's sign-in sessions (web_sessions) and
pages; watch.* events; provider-rate evidence (WATCH_PROVIDER_RATES, off) — the DeepInfra and
Featherless official price documents as immutable observations, the pending change and a person's dated
approval.
Does not own: structured source ingestion for publication (Sources — Watch observes what one person or
agent asked for, not what a product publishes); expensive browser/code execution (Run, step 6) and
local/LAN probes (Link, step 7) — Watch asks them, they run; interpreting a trigger and acting (Actor,
Codes); delivery of a notification (Network consumes watch.watch.triggered); the supply rate card
(AI's catalog — an approved rate change is evidence AI's operator applies).
Who calls it, what it calls
Every /api/v1 route needs a Network token for audience openvibe.watch with that route's one
capability: a service token is judged by its cap claim, a person/agent token by identity
(usr_…/agt_…), and a first-party svc:… may send X-OV-Subject to act for a person (an app or
module may not). Watch calls Events (sources and
outbox), Live/Community/anything through the generic pull carriers, Link for node probes, Run for
browser/code checks (not yet), Actor/Codes/Run for actions, Network (JWKS, sessions, notifications),
and a webhook target only through the guarded POST. The vhost keeps /api/v1/* and /internal/*
loopback-only.
Database and migrations
PostgreSQL ov_watch through openvibe-sdk/db: DATABASE_URL through PgBouncer, owner role for
migrations; migrations/ are applied at boot and only add. Tests use PGlite (slow to boot — 180 s per
file) or WATCH_TEST_STORE=pg; never edit a migration that has run, and rollback must work. A check
writes a check_runs row and observations; a conditional fetch stores its validators only after a
good answer.
Auth, ownership and exposure
Tokens are Network RS256 JWTs verified offline against JWKS. Reads and writes are scoped to the acting
subject; another owner's watch answers 404, not 403. X-OV-Subject is honoured only from a first-party
service. /auth/*, the pages and discovery files are public; the API and /internal/* are
loopback-only; /metrics answers direct loopback callers only. /internal/events is signed and
loopback-only.
Deploy
sudo ovhost deploy watch: checkout /opt/openvibe.watch, unit
deploy/systemd/openvibe-watch.service as openvibe-watch.service on 127.0.0.1:4730, env
/etc/openvibe/watch.env (0600), database ov_watch, nginx
deploy/nginx/openvibe.watch.conf. Rollback with sudo ovhost rollback watch --to <sha>; readiness
reports degraded for an unloaded Network key or a check worker behind by 15 minutes.
Commands and tests
npm install
npm run dev # http://127.0.0.1:4730
npm test # every test/*.test.js against local stub sites
npm test -- carriers check # only matching files
npm run test:pg # the same through PgBouncer (CI path)
test/run.js uses openvibe-shared/test-runner (180 s, serial): temp PGlite, generated RSA keys and
stub subscribers/sites, never the network. Tests cover the carriers and their preference, event and
webhook sources, conditional fetch/ETag, extraction, conditions and observations, actions, budgets,
account export/deletion, provider rates, the pages and discovery.
Conventions and pitfalls
- CommonJS, 4-space indent, single quotes, semicolons; problem+json errors; escaped server HTML.
- Carrier preference is binding: an event or ETag wins over polling; a Watch must not poll when the
source can push or answer 304. A new carrier is added toserver/carriers/index.jsand the README
table, not bolted onto the http path. - Watch asks, others run: no browser/code execution or LAN probing in this repo — call Run/Link
with the person's authority. Expensive work needs a budget. - Every check records its observation state and backoff; a failure is recorded, not retried in a
tight loop, and never invents a change. - Actions are explicit and owner-scoped: a notification, event, webhook, Actor/Codes/Run task are
the person's; never let a watch trigger another person's process. - The public site is complete without JavaScript: create/edit/pause/resume/delete/check-now are
forms; the query picks a template, not a script. - Provider rates are evidence, not an applied price: while
WATCH_PROVIDER_RATESis off, store
the official document, show the pending change and require the dated approval; AI's operator applies
it.