openvibers/openvibe.work AGENTS.md

AGENTS.md

main 6.2 KB · 103 lines · markdown

AGENTS.md — OpenVibe.Work

Project overview

OpenVibe.Work (https://openvibe.work) is the network's job-listings service: it gathers listings from
public job boards, keeps provenance and a short plain-text excerpt, makes them searchable without an
account, and lets a signed-in person save searches, track applications and ask their OpenVibe.Actor
agent for help preparing one. It does not republish a board's listing: every listing links back to the
board, where the reader applies.

  • Entry: server/index.js; app server/app.js; port 4960, service id work, env prefix WORK.
  • API/pages: server/http/ — api.js (/api/v1), pages.js, discovery.js, principal.js,
    caller-limits.js, router.js; rendering server/render/ (listing.js, markdown.js,
    layout.js, html.js, short.js); CSS in public/css.
  • Ingest: server/ingest/ (index.js, from-sources.js) and server/jobs/ (sources.js,
    store.js, text.js, hiring-line.js, job-types.js, places.js, companies.js,
    rederive-hn-titles.js); run it with npm run ingest or the in-process interval.
  • Sources: Arbeitnow, We Work Remotely, RemoteOK and Hacker News "Who is hiring" arrive through
    OpenVibe.Sources (server/ingest/from-sources.js); Remotive is the one board Work still fetches
    itself (its robots.txt disallows /api/*, its API terms grant the named, linked use). Every
    board's terms are applied on every page (/sources, GET /api/v1/sources).
  • Docs/config: README "Data sources and their terms", SECURITY.md, .env.example.

Owns, does not own

Owns its PostgreSQL tables and nothing else: work_listings (upserted on (source, source_id), the
board's id, original url, its fields, a short plain-text excerpt; expired listings kept but hidden),
work_saved_searches (subject = user:usr_…, last looked), work_source_fetches,
work_applications (subject_id, status/notes/dates/next step/last Actor task, one per person per
listing), work_application_events (append-only history), account_data_events and
work_ingest_cursor.

Does not own: identity/accounts (Network — OAuth client work, PKCE S256, JWKS, canonical subject);
event delivery (Events — Work creates its two account subscriptions at boot); the listings themselves
(the boards; Work keeps an excerpt and a link back, and reads each board only on its terms).

Who calls it, what it calls

Anyone can search and read the public API (/api/v1/ping, /jobs, /jobs/:id, /sources) and the
Atom feed (/jobs.atom); a signed-in person uses Network SSO for saved searches, applications and the
Actor assist. SDK openvibe-sdk/work is the public client (jobs.search, jobs.get, sources, and
the person's savedSearches/applications). Work calls Network (SSO/JWKS), Events (subscriptions and
the outbox), the board APIs (only configured base URLs, this service's User-Agent, a timeout and a size
cap, storing only http(s) listing URLs) and OpenVibe.Actor (help prepare an application from the
person's own agent). "Get alerts" hands a saved search to OpenVibe.Watch's feed form; Watch owns the
watching.

Database and migrations

PostgreSQL ov_work through openvibe-sdk/db (PgBouncer for the app, direct owner role for
migrations): 0001 creates nothing, 0002 listings/saved searches/fetches, 0003 account data,
0004 the search index, 0005 applications, plus telemetry, ingest cursor, company slug index, config
and the outbox rejection index. Applied at boot; never edit a migration that has run. The database is
the only required readiness check; Network/JWKS/Valkey are optional and reported per check.

Auth and exposure

Session tokens are httpOnly cookies; a FedCM assertion or an app/service token is never a session.
Public pages/API are anonymous and cacheable; saved searches and applications are the person's own
(user:usr_…), and an application is one per person per listing. /internal/events is signed and
loopback-only; account export/deletion covers saved searches and applications, and a redelivery changes
nothing. Request bodies are never logged; secrets live only in the env file.

Deploy

sudo ovhost deploy work: checkout /opt/openvibe.work, unit openvibe-work.service on
127.0.0.1:4960, env /etc/openvibe/work.env, database ov_work; nginx
deploy/nginx/openvibe.work.conf. Set WORK_INGEST=on in /etc/openvibe/work.env or no listing
will ever arrive.
Rollback with sudo ovhost rollback work --to <sha>.

Commands and tests

npm ci
npm run dev                  # http://127.0.0.1:4960
npm test                     # every test/*.test.js, temp PGlite + mock Network
npm test -- ingest login     # only matching files
npm run test:pg              # the same on PostgreSQL
npm run ingest               # fetch the boards once (needs WORK_INGEST=on)

test/run.js uses openvibe-shared/test-runner; temp PGlite and an in-process Network mock, never the
network. Coverage includes the board adapters and terms, the Hacker News line split/rederive, search
and facets, saved searches, applications and their history, Actor assist, auth/JWKS/limits, discovery,
account data and the pages.

Conventions and pitfalls

  • CommonJS, 4-space indent, single quotes, semicolons; problem+json errors; escaped HTML, and a listing's
    excerpt is plain text (never Markdown/HTML from the board).
  • Never republish a board's listing: keep a short excerpt and link back to the original; name the
    source and apply its terms on every page. A board that asks to be named or linked must stay named.
  • The ingest only fetches configured base URLs, never a URL chosen by a request, with this
    service's User-Agent, a timeout and a size cap; only http(s) listing URLs are stored.
  • Ingest is off unless WORK_INGEST=on — a dev/test run must never call a board; the test suite
    runs with it off.
  • A saved search is keyed by the canonical subject, and "new since you last looked" is computed
    from that row, not from a client-supplied timestamp.
  • One application per person per listing, with an append-only event history; a status change is a
    new event, not an overwritten field.
  • Work does not watch: "Get alerts" opens Watch with the Atom feed; only Watch runs the check and
    the action.