[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-80430":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":15,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":19,"topics":22,"createdAt":10,"pushedAt":10,"updatedAt":43,"readmeContent":44,"aiSummary":45,"trendingCount":15,"starSnapshotCount":15,"syncStatus":46,"lastSyncTime":47,"discoverSource":48},80430,"openxiv","davidichalfyorov-wq\u002Fopenxiv","davidichalfyorov-wq","OpenXiv is an AT Protocol preprint server with source-based submissions, public provenance, typed endorsements, OAI-PMH, and Bluesky federation.","https:\u002F\u002Fopenxiv.net",null,"TypeScript",46,5,13,0,1,2.33,"GNU Affero General Public License v3.0",false,"main",true,[23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42],"astro","atproto","bluesky","digital-library","drizzle-orm","fastify","federation","fediverse","institutional-repository","oai-pmh","open-access","open-science","postgresql","preprint-server","preprints","publishing","scholarly-communication","scientific-publications","self-hosted","typescript","2026-06-12 02:04:02","\u003Cp align=\"center\">\n  \u003Cimg src=\"apps\u002Fapi\u002Fsrc\u002Fservices\u002Fbrand\u002Flogo-full.png\" alt=\"OpenXiv\" width=\"260\" \u002F>\n\u003C\u002Fp>\n\n\u003Ch1 align=\"center\">OpenXiv\u003C\u002Fh1>\n\n\u003Cp align=\"center\">\n  \u003Cstrong>A preprint server that lives in your social feed.\u003C\u002Fstrong>\u003Cbr\u002F>\n  Built on the AT Protocol. Preprints federate to Bluesky.\u003Cbr\u002F>\n  \u003Ca href=\"https:\u002F\u002Fopenxiv.net\">openxiv.net\u003C\u002Fa> · ISSN \u003Ca href=\"https:\u002F\u002Fportal.issn.org\u002Fresource\u002FISSN\u002F3120-9556\">3120-9556\u003C\u002Fa> (online) · \u003Ca href=\"https:\u002F\u002Fwww.wikidata.org\u002Fwiki\u002FQ139860032\">Wikidata Q139860032\u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"LICENSE\">\u003Cimg alt=\"License: AGPL-3.0-or-later\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-AGPL--3.0--or--later-blue.svg\">\u003C\u002Fa>\n  \u003Cimg alt=\"TypeScript\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Ftypescript-strict-3178c6\">\n  \u003Cimg alt=\"Node 22\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fnode-%E2%89%A522-43853d\">\n  \u003Cimg alt=\"AT Protocol\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FAT_Protocol-app.openxiv.*-0085ff\">\n  \u003Cimg alt=\"OAI-PMH 2.0\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FOAI--PMH-2.0-7d3cad\">\n\u003C\u002Fp>\n\n---\n\nOpenXiv is an AT Protocol App View for science. Papers, threads, endorsements,\ndisclosures, and reviews are real `app.openxiv.*` records that live in each\nauthor's PDS (Bluesky's or their own) and federate to the wider AT Protocol\nnetwork. The server publishes to Bluesky, exposes OAI-PMH 2.0 for\nlibrary indexers, and runs its own custom Bluesky feeds.\n\nOpen to independent researchers without institutional backing. Sign in with\nORCID, Google, or did:plc through Bluesky. No endorsement gate. No\n\"must be affiliated\" filter.\n\nAI use is welcome under structured disclosure (`none`, `assistant`,\n`coauthor`, `primary`, recorded as an `app.openxiv.disclosure` record).\nUnverified output is refused with a public refusal packet that names the\nfailure mode. Work returns for revision instead of triggering an author ban.\n\nThe instance at [openxiv.net](https:\u002F\u002Fopenxiv.net) is the single live\ndeployment. It is also indexed by [BASE](https:\u002F\u002Fwww.base-search.net\u002F),\n[CORE](https:\u002F\u002Fcore.ac.uk\u002F), and the [Bluesky directory of custom feeds](https:\u002F\u002Fbsky.app\u002Fprofile\u002Fopenxiv.bsky.social).\n\n## Table of contents\n\n- [What you actually get](#what-you-actually-get)\n- [Stack](#stack)\n- [Repository layout](#repository-layout)\n- [Lexicons (`app.openxiv.*`)](#lexicons-appopenxiv)\n- [Submit saga (6 stages)](#submit-saga-6-stages)\n- [OAI-PMH and library indexing](#oai-pmh-and-library-indexing)\n- [Bluesky feed generator](#bluesky-feed-generator)\n- [arXiv overlay browser extension](#arxiv-overlay-browser-extension)\n- [Quick start (Docker)](#quick-start-docker)\n- [Development](#development)\n- [Configuration](#configuration)\n- [Tests](#tests)\n- [Production deployment](#production-deployment)\n- [Single-owner mode](#single-owner-mode)\n- [Auth flows](#auth-flows)\n- [Contributing](#contributing)\n- [License](#license)\n\n## What you actually get\n\n| Feature | State |\n|---|---|\n| OpenXiv ids `{subject}.{YYYY}.{NNNNN}` allocated per (subject, year) | live, atomic |\n| Profile pages `\u002F@{handle}` with unified All \u002F Papers \u002F Posts tabs | live |\n| Submit wizard, 6 idempotent stages, per-stage retry endpoint | live |\n| Read flow at `\u002Fabs\u002F{id}` with PDF, HTML, three explainer tiers, saga timeline | live |\n| Trust Passport with lanes for transparency, identity, provenance, citations, math, integrity, social review, public disputes, external attestations | live |\n| Provenance Timeline, eight publicly visible stages from upload to Bluesky bridge | live |\n| Real OAuth: ORCID, Google, Bluesky (did:plc), Mastodon (cross-post link) | live, mock fallback for dev |\n| OAI-PMH 2.0 endpoint at `\u002Foai-pmh` with oai_dc metadata, transient deletes, ISO 8601 datestamps | live, validated by BASE OVAL |\n| Bluesky feed generator (six custom feeds at `did:web:openxiv.net`) | live |\n| Refusal packets at `\u002Frefusals\u002F{id}` for AI-slop and policy violations | live |\n| Browser extension that injects OpenXiv Trust Passport badges onto `arxiv.org\u002Fabs\u002F*` pages | dev install |\n| Multi-tier summaries (school, undergrad, expert) generated at submission, editable by author | live |\n| `app.openxiv.preprint` and `app.openxiv.prereg` registry for hypothesis pre-registration | live |\n| `app.openxiv.endorsement` typed verbs (verified derivation, reproduced result, checked references, etc.) | live |\n| Jetstream subscriber that mirrors `app.bsky.feed.post` referencing OpenXiv papers back into the App View | live |\n| DOI minting via Crossref or DataCite | deferred until membership |\n| Federated discovery beyond Bluesky (Mastodon ActivityPub, IPFS replicas) | future |\n\n## Stack\n\n- **Language**: TypeScript strict, Node 22.\n- **API**: [Fastify 5](https:\u002F\u002Ffastify.dev\u002F) with `fastify-type-provider-zod` for end-to-end typed routes.\n- **Database**: PostgreSQL 16 + pgvector via Drizzle ORM. Migrations live in `packages\u002Fdb\u002Fdrizzle`.\n- **Queues**: BullMQ on Redis 7. Heavy work (compile, embed, explain, figure extraction, Bluesky bridge, jetstream relay) runs in a separate worker process.\n- **Storage**: S3 compatible. MinIO locally, Hetzner Object Storage in production, accessed through `@aws-sdk\u002Fclient-s3`.\n- **LaTeX to PDF**: `tectonic` running in a sandboxed Docker container (mockable).\n- **LaTeX to HTML**: `latexml` (mockable). PDF and HTML are served side by side on the paper page.\n- **Metadata extraction**: GROBID over its HTTP API for affiliations, references, abstracts.\n- **Figure extraction**: `pdf-lib` plus a custom raster pass; each figure lands at `\u002Fapi\u002Fpapers\u002F{id}\u002Ffigures\u002F{n}`.\n- **LLM**: DeepSeek V4 Flash for explainer tier generation; Gemini `gemini-embedding-001` for semantic similarity.\n- **Undisclosed AI detector**: ensemble (perplexity burst + Binoculars-style ratio + stylometric) producing a score in `[0, 100]`. The MVP ships a heuristic; swap in GPT-2-medium scoring when the model directory is populated.\n- **Auth**: OAuth via ORCID (primary, verifies researchers), Google, and Bluesky, all resolving to an AT Protocol DID. Mastodon link-only for cross-posting.\n- **Web**: Astro 5 server-rendered with React 19 islands for the submission wizard, the explainer tabs, the publish action, and the discussion thread.\n- **Lexicons**: `app.openxiv.*` records in `packages\u002Flexicons` with JSON schemas and matching zod validators.\n- **Errors**: `neverthrow` Result types end to end. No silent catches.\n- **Resilience**: opossum circuit breakers wrap every external (LLM, GROBID, tectonic, S3, ORCID, Bluesky PDS, jetstream). A single dependency going down does not take the system out.\n- **Edge**: Caddy 2 in front of every service. Automatic HTTPS, HTTP\u002F3, on-the-fly compression.\n- **Observability**: Sentry SDK with `SENTRY_DSN` opt in; pino logs with `pino-pretty` in dev.\n\n## Repository layout\n\n```\nopenxiv\u002F\n  apps\u002F\n    api\u002F              Fastify API + BullMQ workers (one image, two entrypoints).\n      src\u002F\n        routes\u002F       REST endpoints: auth, papers, posts, feed, uploads,\n                      profiles, follows, endorsements, refusals, oai-pmh,\n                      bsky-feed-generator, bsky-labeler, jetstream relay,\n                      starter pack, daily-brief, search, topics, lens,\n                      analytics, engagement, account-linking, did-web,\n                      preregistrations, paper-edit, moderation, versions.\n        services\u002F     Application logic (submissions saga, feed, explain,\n                      posts, users, social-push, jetstream-subscriber,\n                      Trust Passport assembly, refusal packets, mastodon\n                      crosspost, etc).\n        workers\u002F      BullMQ workers (compile, embed, explain, figures,\n                      pdf-finalize, bsky-follow, mastodon-crosspost).\n        plugins\u002F      Fastify plugins (auth, error handler, OpenAPI).\n        auth\u002F         Session JWT signing, OAuth state, did:plc resolver.\n        scripts\u002F      Operator CLIs (sample-cover, register-bsky-feeds,\n                      bsky-smoke, finalize-all-papers, figures-extract-all,\n                      rasterize-brand).\n        services\u002Fbrand\u002F  PDF cover assets (logo + masthead bitmaps).\n        context.ts    DI bag: db, redis, clients, repos.\n        index.ts      API entrypoint.\n        worker.ts     Worker entrypoint, same context + services.\n      Dockerfile      Multi-stage; tini-supervised; runs migrations on\n                      first start.\n\n    web\u002F              Astro 5 SSR + React islands.\n      src\u002F\n        pages\u002F        \u002F, \u002Fsubmit, \u002Fabs\u002F[id], \u002F@[handle], \u002Fauth\u002Fsign-in,\n                      \u002Fabout, \u002Ffaq, \u002Fpress, \u002Fpolicies\u002F*, \u002Fterms, \u002Fprivacy,\n                      \u002Fdmca, \u002Ftransparency, \u002Fglossary, \u002Fvocabulary,\n                      \u002Fbrowse, \u002Ffeeds, \u002Ffeed.atom, \u002Fsitemap.xml, \u002Fstats,\n                      \u002Fsearch, \u002Ftopics\u002F*, \u002Fcompare, \u002Flens\u002F*, \u002Fdocs\u002F*,\n                      \u002Fprereg\u002F*, \u002Fembed\u002F*, \u002Fadmin\u002F*.\n                      \u002Fapi-proxy\u002F[...path] proxies browser to API so\n                      cookies stay same-origin.\n        components\u002F   PaperRow, PostRow, AiBadge, Explainer, SubmissionWizard,\n                      PublishButton, TrustPassportPanel, RefusalRow,\n                      Provenance Timeline, EmbedCard, MastodonLink.\n        layouts\u002F      Base.astro with global head, JSON-LD,\n                      RSS auto-discovery.\n        lib\u002F          api.ts (typed REST client), format.ts, jsonld.ts.\n      public\u002F\n        brand\u002F        logo-mark.svg, logo-full.svg, og-default.svg.\n        humans.txt, llms.txt, llms-full.txt, manifest.json, opensearch.xml,\n        robots.txt, oauth client metadata, schemas\u002F*.json.\n      Dockerfile      Multi-stage; serves with @astrojs\u002Fnode standalone.\n\n    feed-generator\u002F   Standalone Bluesky custom-feed service.\n      src\u002Findex.ts    Implements\n                        \u002F.well-known\u002Fdid.json,\n                        \u002Fxrpc\u002Fapp.bsky.feed.describeFeedGenerator,\n                        \u002Fxrpc\u002Fapp.bsky.feed.getFeedSkeleton.\n                      Six feeds: openxiv-latest, openxiv-featured,\n                      openxiv-questions, openxiv-disclosed,\n                      openxiv-beginner, openxiv-claims. Skeletons\n                      reference the bridged `app.bsky.feed.post` URIs\n                      so the Bluesky App View hydrates embed cards.\n\n    extension\u002F        Chrome \u002F Edge \u002F Firefox MV3 content script.\n      manifest.json   Permission limited to arxiv.org\u002Fabs\u002F*.\n      content.js      Reads arXiv id, calls\n                      ${API_BASE}\u002Fapi\u002Flookup?arxiv_id=...; if matched\n                      shows Trust Passport lane badges in a corner\n                      sidebar; otherwise a \"submit to OpenXiv\" CTA.\n\n  packages\u002F\n    shared\u002F           AppError, Result helpers, zod env schema, category\n                      taxonomy, TID\u002Fat-uri ids, OpenXiv id formatter,\n                      ISSN constants.\n    lexicons\u002F         JSON schemas in schemas\u002F, zod validators in src\u002F.\n                      paper, summary, disclosure, post, review,\n                      endorsement, citation, preprint, prereg.\n    db\u002F               Drizzle schema (users, papers, paper_versions,\n                      paper_figures, paper_extras, paper_edits, posts,\n                      follows, reviews, refusals, embeddings, jobs,\n                      identity, social, bluesky, events, external,\n                      featured, profile-cards, profile-modes).\n                      Repositories + migration runner + index migrations.\n    clients\u002F          External integrations. Each has a real impl, a mock\n                      impl, and a factory that picks via env flags.\n                      storage (S3), llm (DeepSeek + Gemini), compiler\n                      (tectonic), latexml, grobid, oauth (ORCID, Google,\n                      Bluesky), pds (AT Protocol PDS client), detector\n                      (AI ensemble), keywords (KeyBERT-style).\n                      opossum circuit breaker wrapper, HTTP helper with\n                      timeout + retry.\n\n  e2e\u002F                Playwright E2E against the docker-compose stack.\n  docs\u002F               ARCHITECTURE, ops runbook, dev onboarding,\n                      policies, incidents, GITHUB-RELEASE-PREP,\n                      security audit, release checklists.\n  scripts\u002F            Server bootstrap scripts (Contabo VPS),\n                      live-preflight checks (Mastodon, LaTeXML).\n  docker-compose.yml             Local dev stack.\n  docker-compose.production.yml  Contabo production stack.\n  Caddyfile                      Local dev edge.\n  Caddyfile.production           Production edge config.\n  .github\u002Fworkflows\u002F             CI: typecheck + lint + unit tests on\n                                 Node 22 and 24; E2E via docker-compose.\n  _legacy\u002F                       Previous Python + Next.js prototype,\n                                 kept for reference only.\n```\n\n## Lexicons (`app.openxiv.*`)\n\n| Lexicon | Purpose |\n|---|---|\n| `app.openxiv.paper` | A preprint record. Authors, categories, abstract, license, blobs (PDF, source, HTML). |\n| `app.openxiv.summary` | Plain-language summary at `school` \u002F `undergrad` \u002F `expert` tier. Required on submission. |\n| `app.openxiv.disclosure` | Structured AI-use disclosure (`level`, `aiUsed`, `models`, attestation). |\n| `app.openxiv.post` | Short post or thread reply. Shape-compatible with `app.bsky.feed.post` so Bluesky clients render it. |\n| `app.openxiv.review` | Open review with verdict + confidence. |\n| `app.openxiv.endorsement` | Typed verb endorsement: `verified-derivation`, `reproduced-result`, `checked-references`, `useful-background`, `important-but-flawed`, `needs-correction`. |\n| `app.openxiv.citation` | Directed citation, builds the citation graph. |\n| `app.openxiv.preprint` | Lighter-weight record used for the federated preprint timeline. |\n| `app.openxiv.prereg` | Pre-registered hypothesis (research question, prediction, analysis plan, frozen at timestamp). |\n\nJSON schemas live in `packages\u002Flexicons\u002Fschemas\u002F`. Zod validators live in `packages\u002Flexicons\u002Fsrc\u002F`.\n\n## Submit saga (6 stages)\n\nThe submit wizard hands off to a BullMQ saga. Each stage is idempotent and\npersists its outcome in `submission_sagas`. A per-stage retry endpoint is\navailable to the submitter and to admins.\n\n1. `S1 ops_created`: compile (tectonic), upload PDF + HTML to S3, run GROBID for metadata, extract keywords with the KeyBERT-style client, run the undisclosed-AI detector when `level=none`.\n2. `S2 ops_approved`: transition status to `pending_review` (single-instance MVP auto-approves; replace with a moderator gate once the instance opens up).\n3. `S3 id_assigned`: atomic allocation of `openxiv:{primary_category}.{year}.{NNNNN}`.\n4. `S4 pds_paper`: `com.atproto.repo.putRecord` of `app.openxiv.paper` into the author's PDS, status flips to `published`.\n5. `S5 pds_summary_disclosure`: `app.openxiv.summary` + `app.openxiv.disclosure` records into the author's PDS.\n6. `S6 bluesky_bridge`: `app.openxiv.post` referencing the paper, persisted locally so it shows in the OpenXiv feed and is mirrored to the author's `app.bsky.feed.post` bridge record.\n\n## OAI-PMH and library indexing\n\nThe OAI-PMH 2.0 endpoint is at [`https:\u002F\u002Fopenxiv.net\u002Foai-pmh`](https:\u002F\u002Fopenxiv.net\u002Foai-pmh).\n\nSupported verbs: `Identify`, `ListMetadataFormats`, `ListSets`, `ListIdentifiers`, `ListRecords`, `GetRecord`. Metadata format: `oai_dc`. Deleted-record strategy: `transient`. Resumption tokens are stateless and embed `until`, `from`, `set`, and `metadataPrefix` so harvesters can resume mid-page without server state.\n\nThe endpoint is validated by [BASE OVAL](https:\u002F\u002Foval.base-search.net\u002F) and indexed by:\n\n- [BASE](https:\u002F\u002Fwww.base-search.net\u002F), Bielefeld Academic Search Engine.\n- [CORE](https:\u002F\u002Fcore.ac.uk\u002F), Open Access aggregator.\n- Bluesky directory of custom feeds.\n- [Wikidata](https:\u002F\u002Fwww.wikidata.org\u002Fwiki\u002FQ139860032), preprint server entry with ISSN, language, country, headquarters, Bluesky handle.\n\nOpenDOAR submission is in progress.\n\n## Bluesky feed generator\n\n`apps\u002Ffeed-generator\u002F` is a standalone Fastify service that serves the\nBluesky feed-generator XRPC surface:\n\n```\nGET \u002F.well-known\u002Fdid.json                              did:web document\nGET \u002Fxrpc\u002Fapp.bsky.feed.describeFeedGenerator          lists feeds\nGET \u002Fxrpc\u002Fapp.bsky.feed.getFeedSkeleton?feed=at:\u002F\u002F...  returns skeleton\n```\n\nSix feeds are published under `did:web:openxiv.net`:\n\n- `openxiv-latest`: every new paper as it lands.\n- `openxiv-featured`: editor picks.\n- `openxiv-questions`: open questions from researcher threads.\n- `openxiv-disclosed`: papers with explicit AI disclosure (`assistant` and above).\n- `openxiv-beginner`: papers with the strongest \"school tier\" explainer score.\n- `openxiv-claims`: papers with at least one typed `verified-derivation` or `reproduced-result` endorsement.\n\nThe skeleton references the bridged `app.bsky.feed.post` URIs\n(`paper_versions.bsky_post_uri`) so the Bluesky App View hydrates embed\ncards natively.\n\n## arXiv overlay browser extension\n\n`apps\u002Fextension\u002F` is an MV3 content script for Chrome, Edge, and Firefox.\nOn any `https:\u002F\u002Farxiv.org\u002Fabs\u002F*` page it injects a sidebar with the\nmatching OpenXiv paper's Trust Passport lane badges, or a \"submit to\nOpenXiv\" CTA if there is no match.\n\nInstall in developer mode:\n\n1. Open `chrome:\u002F\u002Fextensions` or the browser equivalent.\n2. Enable Developer mode.\n3. Load unpacked, point at `apps\u002Fextension`.\n4. Open the extension's Options page and set the API base URL (default `http:\u002F\u002Flocalhost:4000` for dev).\n5. Visit any `arxiv.org\u002Fabs\u002F{id}` page.\n\nIcons (`icon-16.png`, `icon-48.png`, `icon-128.png`) are placeholders;\nswap before publishing to the Web Store.\n\n## Quick start (Docker)\n\n```bash\ncp .env.example .env       # the defaults already match docker-compose\ndocker compose up --build  # first build ~3 min, subsequent starts ~10s\n```\n\nAfter the stack is healthy:\n\n- **Web**: \u003Chttp:\u002F\u002Flocalhost:4321>\n- **API + OpenAPI docs**: \u003Chttp:\u002F\u002Flocalhost:4000\u002Fdocs>\n- **OAI-PMH**: \u003Chttp:\u002F\u002Flocalhost:4000\u002Foai-pmh?verb=Identify>\n- **Feed generator**: \u003Chttp:\u002F\u002Flocalhost:4400\u002F.well-known\u002Fdid.json>\n- **MinIO console**: \u003Chttp:\u002F\u002Flocalhost:9001> (`minioadmin` \u002F `minioadmin`)\n- **GROBID**: \u003Chttp:\u002F\u002Flocalhost:8070>\n- **Postgres**: `postgres:\u002F\u002Fopenxiv:openxiv@localhost:5432\u002Fopenxiv`\n- **Redis**: `redis:\u002F\u002Flocalhost:6379`\n\nDrizzle migrations run automatically on first start\n(`packages\u002Fdb\u002Fdrizzle\u002F0000_init.sql` and later).\n\nMock clients are on by default (`USE_MOCK_CLIENTS=true`) so the full\nsubmit pipeline works without Gemini, ORCID, or a real tectonic image.\nSwitch them off individually as you wire real providers.\n\n### Happy path, end to end\n\n1. Visit \u003Chttp:\u002F\u002Flocalhost:4321\u002Fauth\u002Fsign-in> and pick **Continue with ORCID** (mock; instantly logs you in as a seeded test author).\n2. Go to **\u002Fsubmit** and walk through the 6-step wizard. Drop any `.tex`, `.pdf`, or `.tar.gz` as the source.\n3. The API stores source to MinIO, creates a `papers` row in `compiling` state, writes the disclosure + summary, ensures a `submission_sagas` row, and enqueues a BullMQ saga job.\n4. The worker runs the 6-stage saga, marking each stage done in `submission_sagas`.\n5. Paper page is at `\u002Fabs\u002F{openxiv-id}` (for example `\u002Fabs\u002Fcs.AI.2026.00001`). The page shows the AI-disclosure badge, the three explainer tabs (school, undergrad, expert), the Trust Passport, the Provenance Timeline, and per-stage retry buttons for the submitter and admins.\n6. Owner profile lives at `\u002F@{handle}` with unified All \u002F Papers \u002F Posts tabs, ORCID and moderator badges.\n\n## Development\n\n```bash\npnpm install\npnpm dev                # API only (port 4000)\npnpm dev:web            # Web only (port 4321)\npnpm dev:workers        # Worker only\npnpm dev:all            # All apps in parallel\n\npnpm typecheck\npnpm lint\npnpm test               # unit + property tests across the workspace\npnpm test:e2e           # Playwright happy path (requires docker compose up)\npnpm build              # build all packages then all apps\npnpm format             # prettier\n```\n\nRun migrations against a local Postgres without containers:\n\n```bash\nDATABASE_URL=postgres:\u002F\u002Fopenxiv:openxiv@localhost:5432\u002Fopenxiv \\\n  pnpm db:migrate\n```\n\nRegenerate a new migration after editing the Drizzle schema:\n\n```bash\npnpm db:generate\n```\n\nFor a first-day setup checklist, see [`docs\u002Fdev\u002FONBOARDING.md`](docs\u002Fdev\u002FONBOARDING.md).\nFor production restart, secret rotation, and restore steps, see\n[`docs\u002Fops\u002FRUNBOOK.md`](docs\u002Fops\u002FRUNBOOK.md).\n\n## Configuration\n\nAll config is validated by `packages\u002Fshared\u002Fsrc\u002Fenv.ts` (zod) on startup.\nIn production the app fails fast on unsafe mock flags, wildcard CORS,\nlocalhost public bases, missing required provider credentials, and\ndefault MinIO credentials. See [`.env.example`](.env.example) for the\nfull list. Highlights:\n\n- `USE_MOCK_CLIENTS=true` flips every external to its in-process mock.\n- `USE_MOCK_LLM`, `USE_MOCK_GROBID`, `USE_MOCK_TECTONIC`, `USE_MOCK_LATEXML`, `USE_MOCK_DETECTOR`, `USE_MOCK_ORCID`, `USE_MOCK_BLUESKY` toggle individual services.\n- `DETECTOR_*_WEIGHT` re-weights the undisclosed-AI ensemble.\n- `ADMIN_DIDS` and `SUBMIT_ALLOW_DIDS` are comma-separated DID lists for admin and submit gates.\n- `JETSTREAM_URL` points at the public Bluesky jetstream relay for the firehose subscriber.\n- `OPENXIV_FLAG_LEGACY_UNPREFIXED_MOUNT=0` retires the legacy unprefixed API surface (default 1 during the migration window).\n\n## Tests\n\n- **Unit (vitest)**: every package and app has a `test` script. Run all with `pnpm test`.\n- **Property-based (fast-check)**: invariants on the disclosure lexicon (`packages\u002Flexicons\u002Fsrc\u002Fdisclosure.property.test.ts`). Non-`none` levels must populate `aiUsed` and `models`. `none` must leave them empty. Cross-listing rules on the paper lexicon are property tested in `paper.crossListings.test.ts`.\n- **Contract**: JSON lexicons are validated against the corresponding zod schemas via the validator tests in `packages\u002Flexicons`.\n- **Integration**: route-level tests under `apps\u002Fapi\u002Fsrc\u002Froutes\u002F*.test.ts` cover the OAI-PMH verbs, the Bluesky starter pack, the moderation flow, account linking, profile assembly, and the events log.\n- **E2E (Playwright)**: `e2e\u002Ftests\u002Fhappy-path.spec.ts` drives the full flow. Sign-in, submit, poll for worker completion, publish, see on home feed.\n\nCI runs the unit job on Node 22 and 24, then the E2E job through\n`docker compose up`. See [`.github\u002Fworkflows\u002Fci.yml`](.github\u002Fworkflows\u002Fci.yml).\n\n## Production deployment\n\nThe single live instance runs on a Contabo Cloud VPS with Caddy 2 in\nfront of `docker-compose.production.yml`. The compose file pins the API\nto the multi-stage image built from `apps\u002Fapi\u002FDockerfile` and the web\nto `apps\u002Fweb\u002FDockerfile`; workers reuse the API image with a different\nentrypoint (`worker.ts`). The feed generator runs as its own service so\nit can be put behind its own subdomain.\n\nBackups: nightly `pg_dump` to an off-site S3, snapshot retention 30\ndays. Object storage in production is Hetzner Object Storage,\nS3-compatible, region `eu-central`.\n\nObservability is opt-in. Set `SENTRY_DSN` to ship server errors.\nLogs are JSON via pino in production; dev uses `pino-pretty`.\n\nFor the full topology, see [`docs\u002FARCHITECTURE.md`](docs\u002FARCHITECTURE.md).\nFor incident response and rotation, see [`docs\u002Fops\u002FRUNBOOK.md`](docs\u002Fops\u002FRUNBOOK.md).\n\n## Single-owner mode\n\nOpenXiv MVP is a single-instance deployment with one owner who is also\nthe only moderator. Wire it via env:\n\n```\nADMIN_DIDS=did:plc:zxyourblueskydid000000001\nSUBMIT_ALLOW_DIDS=did:plc:zxyourblueskydid000000001\n```\n\nOn first sign-in the owner's DID is auto-promoted to `moderator` and\nshown with a badge in the header. With `SUBMIT_ALLOW_DIDS` set, only\nlisted DIDs can hit `POST \u002Fapi\u002Fsubmissions` and random visitors get a\n403. Leave it empty for open submissions.\n\n## Auth flows\n\n- **Mock** (default in dev): the OAuth `authorize` endpoint returns a relative URL with a base64-encoded test profile in `code`. The Astro app proxies `\u002Fapi-proxy\u002Fauth\u002Fdev\u002Fmock-callback` to the API so cookies land on the web origin. Logging in instantly creates `A. Author` and starts a session.\n- **ORCID** (real): register at \u003Chttps:\u002F\u002Forcid.org\u002Fdeveloper-tools> or \u003Chttps:\u002F\u002Fsandbox.orcid.org\u002Fdeveloper-tools> for testing. Set `ORCID_CLIENT_ID`, `ORCID_CLIENT_SECRET`, `ORCID_USE_SANDBOX=true` for sandbox, and flip `USE_MOCK_ORCID=false`. Callback URL must match the public web base, for example `https:\u002F\u002Fopenxiv.net\u002Fapi-proxy\u002Fauth\u002Forcid\u002Fcallback`.\n- **Google** (real): register at \u003Chttps:\u002F\u002Fconsole.cloud.google.com\u002Fapis\u002Fcredentials>. Set `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`. Callback `${PUBLIC_WEB_BASE}\u002Fapi-proxy\u002Fauth\u002Fgoogle\u002Fcallback`.\n- **Bluesky** (real): AT Protocol OAuth (client metadata URL or pre-registered client id). The wire format sits in `packages\u002Fclients\u002Fsrc\u002Foauth\u002Fbluesky.ts`; production should adopt `@atproto\u002Foauth-client-node` for DPoP, PAR, and refresh rotation.\n- **Mastodon** (cross-post link only): the `auth-mastodon` route lets an authenticated OpenXiv user link a Mastodon account so the optional cross-post worker can mirror new paper posts to a Mastodon instance.\n\n## Contributing\n\nSee [`CONTRIBUTING.md`](CONTRIBUTING.md) for local setup, the small-commit\nPR policy, and the TypeScript strict baseline. The project follows\nthe [Contributor Covenant 2.1](CODE_OF_CONDUCT.md).\n\nSecurity reports: see [`SECURITY.md`](SECURITY.md). Private vulnerability\nreports go to the operator address in `apps\u002Fweb\u002Fpublic\u002Fhumans.txt`.\n\nFor architecture context before sending a PR, read\n[`docs\u002FARCHITECTURE.md`](docs\u002FARCHITECTURE.md) and the matching policy\ndocuments under [`docs\u002Fpolicy\u002F`](docs\u002Fpolicy\u002F).\n\n## License\n\n[AGPL-3.0-or-later](LICENSE). Any modified version that talks to users\nover a network must publish its source under the same license.\n\nThis project is a single-operator instance run by David Alfyorov in\nVilnius, Lithuania. Source is open. The data and the indexed records\nremain in each author's PDS under their own control.\n","OpenXiv 是一个基于 AT 协议的预印本服务器，支持源代码提交、公开溯源、类型化背书、OAI-PMH 和 Bluesky 联邦。该项目使用 TypeScript 编写，核心功能包括通过 AT 协议将论文、讨论、背书和评审记录在作者的个人数据存储（PDS）中，并与更广泛的 AT 协议网络进行联邦共享。此外，它还支持 OAI-PMH 2.0 标准，便于图书馆索引器收录，并且能够自动生成 Bluesky 订阅源。OpenXiv 适合独立研究者或无机构支持的研究人员使用，提供 ORCID、Google 或 Bluesky 登录方式，无需经过复杂的审核流程。此项目特别欢迎有结构化披露的 AI 使用，确保了内容的真实性和透明度。",2,"2026-06-11 04:00:42","CREATED_QUERY"]