SPB Git

spb/autoscholar

Public

adopt: état déployé sur M3U96b adopté comme source de vérité (remote-first, 2026-10-30)

mld committed (Oct 30, 2026) Browse files

200 changed files +20,001 −0

200 changed files
added .env.example +34 −0
@@ -0,0 +1,34 @@
1+# AutoScholar.ai — environment reference. No real values here. Copy to .env (never committed).
2+
3+# --- Control plane (apps/web + services/scheduler) ---
4+NODE_ENV=development # development | production | test
5+APP_BASE_URL=http://localhost:8470 # canonical public base URL (https://autoscholar.ai in production)
6+PORT=8470 # web port
7+DATABASE_URL=postgresql://localhost:5432/autoscholar # PostgreSQL 17 connection string
8+ARTIFACT_ROOT=./data/artifacts # filesystem artifact store root (private); public promotions live under <root>/public
9+SESSION_SECRET= # >=32 random bytes, hex; signs session cookies and CSRF tokens
10+ADMIN_BOOTSTRAP_USERNAME=admin # used only when admin_users is empty
11+ADMIN_BOOTSTRAP_PASSWORD= # used only when admin_users is empty; forced rotation at first login; never logged
12+WORKER_ENROLLMENT_SECRET= # one-time secret a new MacLustr worker presents to enroll; rotate after use
13+INTERNAL_API_ALLOWED_CIDRS=127.0.0.1/32,192.168.2.0/24 # networks allowed to reach /api/internal/*
14+TRUST_PROXY=0 # 1 behind the MacLustr Tunnel (Caddy sets X-Forwarded-For)
15+LOG_LEVEL=info
16+SCHEDULER_TICK_MS=5000
17+LEASE_TTL_SECONDS=120
18+DEFAULT_MAX_BUDGET_USD=25
19+DEFAULT_MAX_RUNTIME_MINUTES=240
20+PUBLISHING_ENABLED=1 # emergency switch: 0 halts all automatic publication
21+DISPATCH_ENABLED=1 # emergency switch: 0 pauses all new dispatches
22+STAGING_MODE=0 # 1 = staging/demo content allowed, site marked noindex
23+
24+# --- Worker (services/worker, runs as the dedicated low-privilege user on a MacLustr node) ---
25+CONTROL_PLANE_URL=http://192.168.2.85:8470 # LAN address of the control plane (never the public domain)
26+WORKER_NAME= # e.g. M4M64c
27+WORKER_TOKEN= # issued at enrollment (or WORKER_ENROLLMENT_SECRET for first enrollment)
28+WORKER_ROOT=/var/autoscholar # run workspaces live in <root>/runs/<run-id>
29+WORKER_MAX_CONCURRENT=1
30+ANTHROPIC_API_KEY= # server-side only; research runner subprocess env; never sent to the browser
31+ANTHROPIC_MODEL=claude-opus-5-5 # default research model (overridable per run)
32+RESEARCH_ALLOWED_DOMAINS= # extra egress hostnames allowed inside the sandbox (comma separated)
33+TECTONIC_BIN=/opt/homebrew/bin/tectonic
34+WORKER_MODE=real # real | simulated (simulated is for tests only; results are flagged synthetic)
added .gitignore +18 −0
@@ -0,0 +1,18 @@
1+node_modules/
2+dist/
3+.next/
4+out/
5+*.tsbuildinfo
6+.env
7+.env.local
8+.env.*.local
9+coverage/
10+test-results/
11+playwright-report/
12+.DS_Store
13+/data/
14+/var/
15+*.log
16+.turbo/
17+research/templates/latex/_build/
18+dist-bundle/
added .npmrc +2 −0
@@ -0,0 +1,2 @@
1+verify-deps-before-run=false
2+confirm-modules-purge=false
added README.md +14 −0
@@ -0,0 +1,14 @@
1+# AutoScholar.ai — Autonomous Research Foundry
2+
3+From research instructions to verifiable working papers. Research computed on MacLustr.
4+
5+- `apps/web` — public research library, private admin, HTTP APIs (Next.js)
6+- `services/scheduler` — durable queue, leases, stage state machine, quality gates, publication
7+- `services/worker` — MacLustr worker daemon (runs as a dedicated low-privilege user)
8+- `services/research-runner` — Claude Agent SDK research executor (sandboxed, budgeted)
9+- `services/qa` — deterministic quality gates · `services/publisher` — transactional publication
10+- `packages/*` — db (Drizzle/PostgreSQL), schemas (zod), auth, storage, research-contract, maclustr-adapter, observability, ui
11+- `research/templates` — starter research `CLAUDE.md` files and the LaTeX class
12+- `docs/` — INFRA_DISCOVERY, DECISIONS, IMPLEMENTATION_STATUS, TEST_REPORT, SECURITY, OPERATIONS, RESEARCH_INSTRUCTIONS_GUIDE, LAUNCH_REPORT
13+
14+Local development: `cp .env.example .env`, set `DATABASE_URL`, `SESSION_SECRET`, `ADMIN_BOOTSTRAP_PASSWORD`; then `pnpm install && pnpm db:migrate && pnpm dev` (web on :8470) and `pnpm dev:scheduler`.
added apps/web/AGENTS.md +9 −0
@@ -0,0 +1,9 @@
1+<!-- BEGIN:nextjs-agent-rules -->
2+
3+## This is NOT the Next.js you know
4+
5+This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
6+
7+This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
8+
9+<!-- END:nextjs-agent-rules -->
added apps/web/UI-NOTES.md +33 −0
@@ -0,0 +1,33 @@
1+# UI notes — needs outside the UI scope (for the control-plane owner)
2+
3+## BLOCKER — `packages/core/src/papers.ts` related-papers query
4+
5+`getPaperBySlug` builds `sql\`${paperVersions.keywords} && ${row.v.keywords}\``. Drizzle expands the JS array as a tuple
6+(`&& ($4,$5,$6)`), so PostgreSQL rejects the query and **every paper page returns 500/404** (`/research/[slug]`and`/api/public/papers/[slug]`). Verified fix (one line, import `arrayOverlaps`from`drizzle-orm`):
7+
8+```ts
9+or(eq(papers.discipline, row.p.discipline), arrayOverlaps(paperVersions.keywords, row.v.keywords));
10+```
11+
12+The e2e suite was run with this fix applied temporarily (25/25 pass); the original file was restored afterwards and
13+`packages/core` rebuilt from it, so the paper page is broken again until the fix lands.
14+
15+## Tooling
16+
17+- `biome.json`: add `"css": { "parser": { "tailwindDirectives": true } }` so `apps/web/src/app/globals.css` (`@import "tailwindcss"`, `@theme`, `@apply`, `@custom-variant`) parses; today `pnpm lint` reports parse errors there. Also `biome migrate` (schema 2.4.0 vs CLI 2.6.4; `recommended` deprecated).
18+- pnpm 11 "deps status check" aborted `pnpm start` / `pnpm exec playwright` after dependencies changed (`ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY` → it tried `pnpm install --production`). Workaround used: run binaries directly (`apps/web/node_modules/.bin/next`, `node_modules/.bin/playwright`). Consider `verify-deps-before-run=false` in `.npmrc` for non-interactive hosts (pm2).
19+- `next start` warns that `output: "standalone"` expects `node .next/standalone/server.js` (copy `.next/static` and `public` into the standalone dir on deploy). It still serves correctly; the pm2 manifest should pick one of the two.
20+- Rebuilding `.next` while a `next start` process is still serving leaves it with a stale client manifest ("Could not find the module …link.js in the React Client Manifest" → 500 on every page with `next/link`). Always stop the server before `next build` (or build to a fresh dir and swap).
21+- `packages/db/package.json`: added `pdf-lib` (used only by `src/seed.ts` to generate the staging PDF).
22+
23+## Server helpers / API observations (not changed)
24+
25+- `clientIp()` returns `null` when `TRUST_PROXY=0` and no proxy header is present (Next does not expose the socket address), so locally all visitors share the limiter keys `"unknown"`/`"public"`, and for `/api/internal/*` the CIDR allow-list is skipped (only the bearer token protects it). In production the worker reaches the control plane directly on the LAN (no proxy headers) → same situation. If a LAN-only guard is wanted, bind the internal API to a separate port/listener or set the socket address via a custom server.
26+- Internal worker routes answer **403** (`viaPublicProxy`) to Playwright's request context because it sends `x-forwarded-*`-free requests… actually they return 403 for any request whose headers include forwarded hints; the e2e asserts 401 _or_ 403 for those routes.
27+- The in-memory login limiter (10 logins / 15 min per username) is easy to hit while developing/testing; the e2e suite reuses one signed-in storage state to stay below it. A restart of the web process resets it.
28+- `listEvents()` returns `Date` objects; `redact()` turns a `Date` into `{}` (it walks it as a plain object). The run page converts dates before redacting; the `/api/admin/runs/[id]/events` route passes rows straight through `redact()` → `at` becomes `{}` in the JSON. Suggest `redact()` to pass `Date`/`Buffer` instances through unchanged, or serialise before redacting in that route.
29+- Public artifact route sets `accept-ranges: none`; the browser PDF viewer works but cannot seek by byte range on large PDFs.
30+
31+## Seed
32+
33+`pnpm db:seed` → `seedTopicsAndSeries()` always (17 disciplines + 10 subfields + series `as-wp`), `seedStagingPaper()` only with `STAGING_MODE=1` (one synthetic paper `AS-WP-2026-0000`, `papers.synthetic=true`, 6-page PDF written under `ARTIFACT_ROOT/public/AS-WP-2026-0000/v1/manuscript/main.pdf` with a `.sha256` sidecar). Production (`STAGING_MODE=0`) never lists, serves or indexes it. Remove it before launch with `delete from papers where public_id='AS-WP-2026-0000'` (cascades) + delete the public object.
added apps/web/next-env.d.ts +7 −0
@@ -0,0 +1,7 @@
1+/// <reference types="next" />
2+/// <reference types="next/image-types/global" />
3+import "./.next/types/routes.d.ts";
4+import "./.next/types/root-params.d.ts";
5+
6+// NOTE: This file should not be edited
7+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
added apps/web/next.config.ts +48 −0
@@ -0,0 +1,48 @@
1+import type { NextConfig } from "next";
2+
3+const isProd = process.env.NODE_ENV === "production";
4+const staging = process.env.STAGING_MODE === "1";
5+
6+const securityHeaders = [
7+ { key: "X-Content-Type-Options", value: "nosniff" },
8+ { key: "X-Frame-Options", value: "SAMEORIGIN" },
9+ { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
10+ { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=(), browsing-topics=()" },
11+ {
12+ key: "Content-Security-Policy",
13+ value: [
14+ "default-src 'self'",
15+ `script-src 'self'${isProd ? "" : " 'unsafe-eval'"} 'unsafe-inline'`,
16+ "style-src 'self' 'unsafe-inline'",
17+ "img-src 'self' data: blob:",
18+ "font-src 'self' data:",
19+ "connect-src 'self'",
20+ "frame-src 'self' blob:",
21+ "object-src 'self'",
22+ "base-uri 'self'",
23+ "form-action 'self'",
24+ "frame-ancestors 'self'",
25+ ...(isProd ? ["upgrade-insecure-requests"] : []),
26+ ].join("; "),
27+ },
28+ ...(isProd ? [{ key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" }] : []),
29+ ...(staging ? [{ key: "X-Robots-Tag", value: "noindex, nofollow" }] : []),
30+];
31+
32+const nextConfig: NextConfig = {
33+ reactStrictMode: true,
34+ poweredByHeader: false,
35+ serverExternalPackages: ["pg", "pino", "@node-rs/argon2", "pdfjs-dist", "pdf-lib", "drizzle-orm"],
36+ experimental: { serverActions: { bodySizeLimit: "2mb" } },
37+ async headers() {
38+ return [
39+ { source: "/(.*)", headers: securityHeaders },
40+ {
41+ source: "/papers/:path*",
42+ headers: [{ key: "Cache-Control", value: "public, max-age=31536000, immutable" }],
43+ },
44+ ];
45+ },
46+};
47+
48+export default nextConfig;
added apps/web/package.json +38 −0
@@ -0,0 +1,38 @@
1+{
2+ "name": "@autoscholar/web",
3+ "version": "0.1.0",
4+ "private": true,
5+ "type": "module",
6+ "scripts": {
7+ "dev": "next dev -p ${PORT:-8470}",
8+ "build": "next build",
9+ "start": "next start -H 0.0.0.0 -p ${PORT:-8470}",
10+ "typecheck": "tsc --noEmit"
11+ },
12+ "dependencies": {
13+ "@autoscholar/auth": "workspace:*",
14+ "@autoscholar/core": "workspace:*",
15+ "@autoscholar/db": "workspace:*",
16+ "@autoscholar/observability": "workspace:*",
17+ "@autoscholar/publisher": "workspace:*",
18+ "@autoscholar/qa": "workspace:*",
19+ "@autoscholar/research-contract": "workspace:*",
20+ "@autoscholar/schemas": "workspace:*",
21+ "@autoscholar/storage": "workspace:*",
22+ "drizzle-orm": "^0.45.0",
23+ "marked": "^16.0.0",
24+ "next": "^16.4.0",
25+ "react": "^19.3.0",
26+ "react-dom": "^19.3.0",
27+ "sanitize-html": "^2.17.0",
28+ "zod": "^4.1.0"
29+ },
30+ "devDependencies": {
31+ "@tailwindcss/postcss": "^4.1.0",
32+ "@types/react": "^19.2.0",
33+ "@types/react-dom": "^19.2.0",
34+ "@types/sanitize-html": "^2.16.0",
35+ "tailwindcss": "^4.1.0",
36+ "typescript": "^5.9.0"
37+ }
38+}
added apps/web/postcss.config.mjs +1 −0
@@ -0,0 +1 @@
1+export default { plugins: { "@tailwindcss/postcss": {} } };
added apps/web/src/app/(public)/about/page.tsx +55 −0
@@ -0,0 +1,55 @@
1+import type { Metadata } from "next";
2+import Link from "next/link";
3+
4+export const metadata: Metadata = { title: "About", alternates: { canonical: "/about" } };
5+
6+export default function AboutPage() {
7+ return (
8+ <div className="prose-paper mx-auto max-w-3xl">
9+ <p className="eyebrow" style={{ fontFamily: "var(--font-sans)" }}>
10+ About
11+ </p>
12+ <h1>AutoScholar.ai</h1>
13+ <p>
14+ AutoScholar is a research-producing startup and a public library of original working papers produced
15+ by an autonomous, auditable AI research workflow. The product is not a chatbot, an essay generator or
16+ a subscription for spun articles. It is a steadily growing body of research whose every release
17+ carries its own evidence.
18+ </p>
19+ <h2>What we are</h2>
20+ <p>
21+ A one-person founder operation with an automated research pipeline. A single administrator writes
22+ research instructions, launches runs on our compute infrastructure, and the platform plans,
23+ investigates the literature, executes analyses or proofs, writes a LaTeX manuscript, subjects it to an
24+ independent automated critical review, rebuilds and verifies the result, and publishes it only when
25+ deterministic quality gates pass.
26+ </p>
27+ <h2>What we are not</h2>
28+ <ul>
29+ <li>
30+ We are not a journal. AutoScholar working papers are preprints; they are{" "}
31+ <strong>not peer reviewed</strong> by humans.
32+ </li>
33+ <li>
34+ We do not have an editorial board, invented authors or academic affiliations. The byline of every
35+ paper is “AutoScholar — AI-generated research”.
36+ </li>
37+ <li>We do not register DOIs. Papers are identified by stable AutoScholar working-paper numbers.</li>
38+ <li>We do not report view counts, citation counts or ratings that we cannot verify.</li>
39+ </ul>
40+ <h2>Automation disclosure</h2>
41+ <p>
42+ Research planning, source retrieval, code, analysis, writing and automated review are performed by
43+ large language models (Claude, via the Anthropic API) orchestrated by platform software with enforced
44+ permissions and budgets. Humans set the research questions, configure policies, review gate outcomes,
45+ and can correct or retract any paper. See the <Link href="/methodology">methodology</Link> and{" "}
46+ <Link href="/transparency">transparency</Link> pages.
47+ </p>
48+ <h2>Infrastructure</h2>
49+ <p>
50+ Research is computed on MacLustr, an Apple Silicon research compute infrastructure. See{" "}
51+ <Link href="/infrastructure">Powered by MacLustr</Link>.
52+ </p>
53+ </div>
54+ );
55+}
added apps/web/src/app/(public)/infrastructure/page.tsx +57 −0
@@ -0,0 +1,57 @@
1+import type { Metadata } from "next";
2+
3+export const metadata: Metadata = {
4+ title: "Infrastructure — Powered by MacLustr",
5+ alternates: { canonical: "/infrastructure" },
6+};
7+
8+export default function InfrastructurePage() {
9+ return (
10+ <div className="mx-auto max-w-3xl">
11+ <p className="eyebrow">Powered by MacLustr</p>
12+ <h1 className="serif mt-1 text-3xl font-semibold">Research computed on MacLustr</h1>
13+ <p className="muted mt-3 leading-relaxed">
14+ AutoScholar is the research startup. MacLustr is its compute infrastructure: a private cluster of
15+ Apple Silicon Macs operated as a research compute fabric, with a control plane that schedules work,
16+ isolates each study and records what happened.
17+ </p>
18+ <div className="mt-8 grid gap-4 sm:grid-cols-2">
19+ {[
20+ [
21+ "Apple Silicon nodes",
22+ "Research jobs run on Apple Silicon Mac nodes with unified memory, well suited to numerical work, local model inference and LaTeX builds.",
23+ ],
24+ [
25+ "Isolated per-run workspaces",
26+ "Each run executes under a dedicated low-privilege service identity in a fresh workspace with OS-enforced sandboxing, restricted egress, and time, disk, memory and budget limits.",
27+ ],
28+ [
29+ "Durable scheduling",
30+ "A durable queue with leases, heartbeats and fencing tokens means a run survives page reloads, network blips and worker restarts. Expired workers can never publish stale results.",
31+ ],
32+ [
33+ "Evidence first",
34+ "Every artifact is hashed and versioned on write. Publication is a separate service the research agent cannot call.",
35+ ],
36+ ].map(([h, p]) => (
37+ <div key={h} className="surface p-5">
38+ <h2 className="serif text-lg font-semibold">{h}</h2>
39+ <p className="muted mt-2 text-sm leading-relaxed">{p}</p>
40+ </div>
41+ ))}
42+ </div>
43+ <h2 className="serif mt-12 text-xl font-semibold">What we publish about the infrastructure</h2>
44+ <p className="muted mt-2 text-sm leading-relaxed">
45+ We credit MacLustr on every paper and describe its design honestly. We do not publish node names,
46+ addresses, capacities beyond what is measured, customer information or network topology. Claims about
47+ hardware or performance are limited to what the current inventory and measurements support.
48+ </p>
49+ <h2 className="serif mt-10 text-xl font-semibold">Model execution</h2>
50+ <p className="muted mt-2 text-sm leading-relaxed">
51+ Research runs use the Claude Agent SDK with API-key authentication. The platform records model
52+ identifiers, token usage and cost estimates per stage; these appear in each paper’s provenance panel
53+ where disclosure is appropriate.
54+ </p>
55+ </div>
56+ );
57+}
added apps/web/src/app/(public)/layout.tsx +18 −0
@@ -0,0 +1,18 @@
1+import type { ReactNode } from "react";
2+import { SiteFooter } from "@/components/SiteFooter";
3+import { SiteHeader } from "@/components/SiteHeader";
4+
5+export default function PublicLayout({ children }: { children: ReactNode }) {
6+ return (
7+ <>
8+ <a href="#main" className="skip-link">
9+ Skip to content
10+ </a>
11+ <SiteHeader />
12+ <main id="main" className="container-x py-10">
13+ {children}
14+ </main>
15+ <SiteFooter />
16+ </>
17+ );
18+}
added apps/web/src/app/(public)/legal/privacy/page.tsx +23 −0
@@ -0,0 +1,23 @@
1+import type { Metadata } from "next";
2+export const metadata: Metadata = { title: "Privacy", alternates: { canonical: "/legal/privacy" } };
3+export default function Privacy() {
4+ return (
5+ <div className="prose-paper mx-auto max-w-3xl">
6+ <h1>Privacy</h1>
7+ <p>
8+ Reading the library requires no account and no cookies for visitors. We keep standard server logs (IP
9+ address, user agent, requested URL, timestamp) for security and operations for a limited period. We do
10+ not use third-party analytics, advertising or tracking.
11+ </p>
12+ <p>
13+ The private administration area sets a session cookie for its single administrator. Research
14+ instructions, run logs and artifacts are stored privately until a paper is published; published
15+ bundles are public and permanent, with private provenance elements redacted.
16+ </p>
17+ <p>
18+ Questions about this policy can be sent to the operator through the contact details published on the
19+ About page when available.
20+ </p>
21+ </div>
22+ );
23+}
added apps/web/src/app/(public)/legal/research-disclaimer/page.tsx +32 −0
@@ -0,0 +1,32 @@
1+import type { Metadata } from "next";
2+export const metadata: Metadata = {
3+ title: "Research disclaimer",
4+ alternates: { canonical: "/legal/research-disclaimer" },
5+};
6+export default function Disclaimer() {
7+ return (
8+ <div className="prose-paper mx-auto max-w-3xl">
9+ <h1>Research and AI disclaimer</h1>
10+ <p>
11+ <strong>
12+ Every AutoScholar paper is an AI-generated working paper. It has not been peer reviewed.
13+ </strong>{" "}
14+ It is a preprint produced by an autonomous workflow using large language models, verified by automated
15+ checks that test structure, traceability and consistency, not truth.
16+ </p>
17+ <p>
18+ Nothing on this site is medical, legal, financial or engineering advice. Results may contain errors,
19+ including errors that the automated gates cannot detect. Readers who rely on a result should reproduce
20+ it from the released code and data and consult qualified humans.
21+ </p>
22+ <p>
23+ No paper carries a DOI, a human author, an institutional affiliation, an ethics approval or a claim of
24+ peer review unless such a fact is explicitly and verifiably stated on the paper page. Any such
25+ statement elsewhere is false.
26+ </p>
27+ <p>
28+ MacLustr is credited as compute infrastructure. It is not a coauthor and does not endorse any result.
29+ </p>
30+ </div>
31+ );
32+}
added apps/web/src/app/(public)/legal/terms/page.tsx +27 −0
@@ -0,0 +1,27 @@
1+import type { Metadata } from "next";
2+export const metadata: Metadata = { title: "Terms of use", alternates: { canonical: "/legal/terms" } };
3+export default function Terms() {
4+ return (
5+ <div className="prose-paper mx-auto max-w-3xl">
6+ <h1>Terms of use</h1>
7+ <p>
8+ AutoScholar working papers and their accompanying artifacts are provided as is, without warranty of
9+ any kind. Licences for paper text, code and data are stated separately on each paper page when they
10+ were part of the research; where no licence is stated, all rights are reserved.
11+ </p>
12+ <p>
13+ You may read, download and cite papers. Citation guidance is provided on each paper page. You may not
14+ misrepresent an AutoScholar working paper as a peer-reviewed publication or as the work of a human
15+ author.
16+ </p>
17+ <p>
18+ The site, its API and its feeds may be rate-limited. Automated access must respect robots directives.
19+ We may correct or retract papers at any time; corrections and retractions are public and permanent.
20+ </p>
21+ <p>
22+ These terms do not create any legal guarantee of accuracy, fitness for a purpose or regulatory
23+ compliance.
24+ </p>
25+ </div>
26+ );
27+}
added apps/web/src/app/(public)/methodology/page.tsx +72 −0
@@ -0,0 +1,72 @@
1+import { STAGES } from "@autoscholar/schemas";
2+import type { Metadata } from "next";
3+
4+export const metadata: Metadata = { title: "Methodology", alternates: { canonical: "/methodology" } };
5+
6+const DETAILS: Record<string, string> = {
7+ preflight:
8+ "The frozen research instruction is parsed; scientific scope, feasibility, restricted or regulated activities, missing inputs and budgets are assessed. Unsafe or impossible studies are rejected with reasons.",
9+ framing:
10+ "A precise question, hypotheses or propositions, the intended contribution and a discipline-appropriate methodology are recorded, with evaluation criteria fixed before any test is run and honest null-result pathways identified.",
11+ literature:
12+ "Real scholarly registries (Crossref, OpenAlex, arXiv, PubMed, Semantic Scholar as applicable) are searched. Each reference is verified by identifier before it can be cited; a claim-to-source map is built. No reference is cited merely because a model believes it exists.",
13+ design:
14+ "Depending on the mode: genuine, legally usable data with documented provenance and licence; formal definitions and planned derivations; benchmark environments with baselines, seeds and leakage controls; or a search protocol with inclusion criteria. Synthetic simulations are labelled as synthetic.",
15+ execute:
16+ "Real code, experiments, simulations, formal checks or substantive qualitative work are executed in an isolated workspace. Environment, versions, seeds, commands and outputs are logged, including failures and negative findings.",
17+ robustness:
18+ "Sensitivity analyses, alternative specifications, stronger baselines, stress tests, proof checks or counterexample searches are run, and results that weaken the contribution are reported.",
19+ manuscript:
20+ "A LaTeX manuscript is generated from the recorded results with a real BibTeX database; every reported number must derive from stored artifacts.",
21+ review:
22+ "A separate review context that did not author the manuscript critiques novelty, validity, evidence, mathematics, statistics, references, ethics and writing. A response-to-review document records revisions. This is automated critique, not human peer review.",
23+ reproduction:
24+ "Scripts are re-executed and the PDF is rebuilt in a clean environment. Tables, figures, arithmetic, citation identifiers, fonts and build health are checked; representative pages are inspected.",
25+ quality:
26+ "Deterministic gates run on the control plane, independent of the authoring model. A hard failure blocks publication regardless of any review score.",
27+ publication:
28+ "A permanent paper number is allocated, artifacts are promoted with their digests, and the paper, feed and sitemap are updated transactionally. Automatic release requires every hard gate to pass; sensitive modes require human approval.",
29+};
30+
31+export default function MethodologyPage() {
32+ return (
33+ <div className="mx-auto max-w-3xl">
34+ <p className="eyebrow">Methodology</p>
35+ <h1 className="serif mt-1 text-3xl font-semibold">How a working paper is produced</h1>
36+ <p className="muted mt-3">
37+ Production is a durable state machine of stages with evidence requirements, not a single enormous
38+ prompt and not a cast of fictional named agents. The platform policy controls permissions, sandboxes,
39+ budgets and publication; the uploaded research instruction controls only the academic task.
40+ </p>
41+ <ol className="mt-8 space-y-6">
42+ {STAGES.map((s) => (
43+ <li key={s.key} className="grid gap-2 border-t pt-4 hairline sm:grid-cols-[4rem_1fr]">
44+ <span className="font-mono text-sm subtle">Stage {s.index}</span>
45+ <div>
46+ <h2 className="serif text-lg font-semibold">{s.name}</h2>
47+ <p className="muted mt-1 text-sm leading-relaxed">{DETAILS[s.key]}</p>
48+ </div>
49+ </li>
50+ ))}
51+ </ol>
52+ <h2 className="serif mt-12 text-xl font-semibold">Research modes</h2>
53+ <p className="muted mt-2 text-sm">
54+ Empirical, computational, theoretical, review, experimental and humanities modes each carry their own
55+ required artifacts and checks. Mixed-mode studies are allowed; a template never forces an experiment
56+ or a dataset that does not belong. Human participants, clinical interventions or biosafety-relevant
57+ work are out of scope unless documented approvals exist; otherwise work is restricted to lawful
58+ literature, public data, mathematics and simulations.
59+ </p>
60+ <h2 className="serif mt-10 text-xl font-semibold">Research standards</h2>
61+ <ul className="muted mt-2 list-disc space-y-1 pl-5 text-sm">
62+ <li>Length is not quality. A concise correct result outranks a long decorated one.</li>
63+ <li>
64+ No invented references, datasets, results, proofs, significance, approvals, affiliations, identities
65+ or reproducibility claims.
66+ </li>
67+ <li>No silent re-specification until a desired p-value appears; negative findings are preserved.</li>
68+ <li>All external content is untrusted data; it cannot alter platform rules.</li>
69+ </ul>
70+ </div>
71+ );
72+}
added apps/web/src/app/(public)/page.tsx +140 −0
@@ -0,0 +1,140 @@
1+import { disciplineCounts, listTopics, searchPapers } from "@autoscholar/core";
2+import { DISCIPLINE_LABELS, PublicSearchQuery } from "@autoscholar/schemas";
3+import Link from "next/link";
4+import { Disclosure } from "@/components/Disclosure";
5+import { EmptyState } from "@/components/EmptyState";
6+import { PaperCard } from "@/components/PaperCard";
7+import { t } from "@/lib/i18n";
8+import { db } from "@/lib/server/db";
9+
10+export const dynamic = "force-dynamic";
11+
12+export default async function Home() {
13+ const d = db();
14+ const latest = await searchPapers(d, PublicSearchQuery.parse({ pageSize: "7" }));
15+ const [featured, ...recent] = latest.items;
16+ const counts = await disciplineCounts(d);
17+ const topics = await listTopics(d);
18+ return (
19+ <div className="space-y-20">
20+ <section className="grid gap-10 md:grid-cols-12 md:items-end">
21+ <div className="md:col-span-8">
22+ <p className="eyebrow">Autonomous Research Foundry</p>
23+ <h1 className="serif mt-3 text-4xl font-semibold leading-[1.1] tracking-tight sm:text-5xl lg:text-6xl">
24+ From research instructions to verifiable working papers.
25+ </h1>
26+ <p className="muted mt-6 max-w-2xl text-lg leading-relaxed">
27+ AutoScholar is a research-producing startup and a public library of original, AI-executed working
28+ papers across disciplines. Each paper is produced by an auditable autonomous workflow, checked by
29+ deterministic quality gates, and released with its evidence: sources, code, data, build logs and
30+ provenance.
31+ </p>
32+ <div className="mt-8 flex flex-wrap gap-3">
33+ <Link href="/research" className="btn btn-primary">
34+ Browse the library
35+ </Link>
36+ <Link href="/methodology" className="btn">
37+ How papers are produced
38+ </Link>
39+ </div>
40+ </div>
41+ <aside className="surface p-5 text-sm md:col-span-4">
42+ <p className="eyebrow">Powered by MacLustr</p>
43+ <p className="muted mt-2">
44+ {t("footer.credit")} Every run executes in an isolated workspace with enforced budgets, and
45+ nothing is published unless every hard gate passes.
46+ </p>
47+ <Link href="/infrastructure" className="mt-3 inline-block">
48+ About the infrastructure →
49+ </Link>
50+ </aside>
51+ </section>
52+
53+ <section aria-labelledby="latest">
54+ <div className="flex items-end justify-between gap-4">
55+ <div>
56+ <p className="eyebrow">Library</p>
57+ <h2 id="latest" className="serif mt-1 text-2xl font-semibold">
58+ New releases
59+ </h2>
60+ </div>
61+ <Link href="/research" className="text-sm">
62+ All working papers →
63+ </Link>
64+ </div>
65+ <div className="mt-6">
66+ {!featured ? (
67+ <EmptyState title="No working papers have been released yet.">{t("library.empty")}</EmptyState>
68+ ) : (
69+ <div className="grid gap-4 md:grid-cols-12">
70+ <div className="md:col-span-7">
71+ <PaperCard p={featured} featured />
72+ </div>
73+ <div className="grid gap-4 md:col-span-5">
74+ {recent.slice(0, 3).map((p) => (
75+ <PaperCard key={p.publicId} p={p} />
76+ ))}
77+ </div>
78+ {recent.slice(3).map((p) => (
79+ <div key={p.publicId} className="md:col-span-4">
80+ <PaperCard p={p} />
81+ </div>
82+ ))}
83+ </div>
84+ )}
85+ </div>
86+ <div className="mt-6">
87+ <Disclosure />
88+ </div>
89+ </section>
90+
91+ <section aria-labelledby="subjects">
92+ <p className="eyebrow">Subject explorer</p>
93+ <h2 id="subjects" className="serif mt-1 text-2xl font-semibold">
94+ Disciplines
95+ </h2>
96+ <ul className="mt-6 grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
97+ {Object.entries(DISCIPLINE_LABELS).map(([slug, name]) => {
98+ const n = counts.find((c) => c.discipline === slug)?.n ?? 0;
99+ const topic = topics.find((x) => x.slug === slug);
100+ return (
101+ <li key={slug}>
102+ <Link
103+ href={topic ? `/topics/${slug}` : `/research?discipline=${slug}`}
104+ className="surface flex items-center justify-between px-4 py-3 text-sm no-underline hover:bg-[var(--bg-muted)]"
105+ >
106+ <span>{name}</span>
107+ <span className="subtle font-mono text-xs">
108+ {n} {n === 1 ? "paper" : "papers"}
109+ </span>
110+ </Link>
111+ </li>
112+ );
113+ })}
114+ </ul>
115+ </section>
116+
117+ <section className="grid gap-6 md:grid-cols-3">
118+ {[
119+ [
120+ "Scientific integrity before throughput",
121+ "No paper is published automatically unless its central claims trace to verified sources or executed artifacts, its citations resolve, and its PDF builds from the released LaTeX source.",
122+ ],
123+ [
124+ "Evidence travels with the paper",
125+ "Each release ships the manuscript source, verified bibliography, code and data when they were part of the study, a reproducibility note, and a redacted provenance record.",
126+ ],
127+ [
128+ "Honest about what this is",
129+ "These are AI-generated working papers, not peer-reviewed articles. Automated review is critique, not validation. Corrections and retractions are public and permanent.",
130+ ],
131+ ].map(([h, p]) => (
132+ <div key={h} className="border-t pt-4 hairline">
133+ <h3 className="serif text-lg font-semibold">{h}</h3>
134+ <p className="muted mt-2 text-sm leading-relaxed">{p}</p>
135+ </div>
136+ ))}
137+ </section>
138+ </div>
139+ );
140+}
added apps/web/src/app/(public)/research/[slug]/cite.bib/route.ts +29 −0
@@ -0,0 +1,29 @@
1+import { bibtexFor, getPaperBySlug } from "@autoscholar/core";
2+import { handler } from "@/lib/server/api";
3+import { db } from "@/lib/server/db";
4+
5+export const runtime = "nodejs";
6+
7+export const GET = handler(async (_req, { params }) => {
8+ const { slug } = (await params) as { slug: string };
9+ const p = await getPaperBySlug(db(), slug);
10+ const base = process.env.APP_BASE_URL ?? "http://localhost:8470";
11+ const pub = new Date(p.version.publishedAt);
12+ const text = bibtexFor({
13+ publicId: p.paper.publicId,
14+ title: p.version.title,
15+ year: pub.getUTCFullYear(),
16+ month: pub.getUTCMonth() + 1,
17+ versionNo: p.version.versionNo,
18+ url: `${base}/research/${p.paper.slug}`,
19+ keywords: p.version.keywords,
20+ abstract: p.version.abstract,
21+ });
22+ return new Response(text, {
23+ headers: {
24+ "content-type": "application/x-bibtex; charset=utf-8",
25+ "content-disposition": `attachment; filename="${p.paper.publicId}.bib"`,
26+ "cache-control": "public, max-age=3600",
27+ },
28+ });
29+});
added apps/web/src/app/(public)/research/[slug]/cite.ris/route.ts +29 −0
@@ -0,0 +1,29 @@
1+import { getPaperBySlug, risFor } from "@autoscholar/core";
2+import { handler } from "@/lib/server/api";
3+import { db } from "@/lib/server/db";
4+
5+export const runtime = "nodejs";
6+
7+export const GET = handler(async (_req, { params }) => {
8+ const { slug } = (await params) as { slug: string };
9+ const p = await getPaperBySlug(db(), slug);
10+ const base = process.env.APP_BASE_URL ?? "http://localhost:8470";
11+ const pub = new Date(p.version.publishedAt);
12+ const text = risFor({
13+ publicId: p.paper.publicId,
14+ title: p.version.title,
15+ year: pub.getUTCFullYear(),
16+ month: pub.getUTCMonth() + 1,
17+ versionNo: p.version.versionNo,
18+ url: `${base}/research/${p.paper.slug}`,
19+ keywords: p.version.keywords,
20+ abstract: p.version.abstract,
21+ });
22+ return new Response(text, {
23+ headers: {
24+ "content-type": "application/x-research-info-systems; charset=utf-8",
25+ "content-disposition": `attachment; filename="${p.paper.publicId}.ris"`,
26+ "cache-control": "public, max-age=3600",
27+ },
28+ });
29+});
added apps/web/src/app/(public)/research/[slug]/cite.txt/route.ts +29 −0
@@ -0,0 +1,29 @@
1+import { getPaperBySlug, plainCitationFor } from "@autoscholar/core";
2+import { handler } from "@/lib/server/api";
3+import { db } from "@/lib/server/db";
4+
5+export const runtime = "nodejs";
6+
7+export const GET = handler(async (_req, { params }) => {
8+ const { slug } = (await params) as { slug: string };
9+ const p = await getPaperBySlug(db(), slug);
10+ const base = process.env.APP_BASE_URL ?? "http://localhost:8470";
11+ const pub = new Date(p.version.publishedAt);
12+ const text = plainCitationFor({
13+ publicId: p.paper.publicId,
14+ title: p.version.title,
15+ year: pub.getUTCFullYear(),
16+ month: pub.getUTCMonth() + 1,
17+ versionNo: p.version.versionNo,
18+ url: `${base}/research/${p.paper.slug}`,
19+ keywords: p.version.keywords,
20+ abstract: p.version.abstract,
21+ });
22+ return new Response(text, {
23+ headers: {
24+ "content-type": "text/plain; charset=utf-8",
25+ "content-disposition": `attachment; filename="${p.paper.publicId}.txt"`,
26+ "cache-control": "public, max-age=3600",
27+ },
28+ });
29+});
added apps/web/src/app/(public)/research/[slug]/page.tsx +665 −0
@@ -0,0 +1,665 @@
1+import { bibtexFor, getPaperBySlug, plainCitationFor, risFor } from "@autoscholar/core";
2+import { parseBib } from "@autoscholar/qa";
3+import { publicArtifactKey } from "@autoscholar/storage";
4+import type { Metadata } from "next";
5+import Link from "next/link";
6+import { notFound } from "next/navigation";
7+import { CopyButton } from "@/components/CopyButton";
8+import { Disclosure } from "@/components/Disclosure";
9+import { PaperCard } from "@/components/PaperCard";
10+import { PdfViewer } from "@/components/PdfViewer";
11+import { StatusBadge } from "@/components/StatusBadge";
12+import { Tabs } from "@/components/Tabs";
13+import { disciplineLabel, fmtBytes, fmtDate, modeLabel, shortHash } from "@/lib/format";
14+import { t } from "@/lib/i18n";
15+import { db, storage } from "@/lib/server/db";
16+
17+export const dynamic = "force-dynamic";
18+
19+async function load(slug: string) {
20+ try {
21+ return await getPaperBySlug(db(), slug);
22+ } catch {
23+ return null;
24+ }
25+}
26+
27+export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
28+ const { slug } = await params;
29+ const p = await load(slug);
30+ if (!p) return { title: "Paper not found" };
31+ const base = process.env.APP_BASE_URL ?? "http://localhost:8470";
32+ return {
33+ title: p.version.title,
34+ description: p.version.abstract.slice(0, 300),
35+ alternates: { canonical: `/research/${p.paper.slug}` },
36+ robots: p.paper.synthetic ? { index: false, follow: false } : undefined,
37+ openGraph: {
38+ title: p.version.title,
39+ description: p.version.abstract.slice(0, 300),
40+ type: "article",
41+ url: `${base}/research/${p.paper.slug}`,
42+ publishedTime: p.version.publishedAt.toISOString(),
43+ },
44+ other: {
45+ citation_title: p.version.title,
46+ citation_publication_date: p.version.publishedAt.toISOString().slice(0, 10),
47+ citation_technical_report_number: p.paper.publicId,
48+ citation_technical_report_institution: "AutoScholar.ai",
49+ citation_pdf_url: `${base}${p.version.pdfPublicPath}`,
50+ },
51+ };
52+}
53+
54+type ArtifactEntry = {
55+ path: string;
56+ kind: string;
57+ sha256: string;
58+ bytes: number;
59+ contentType: string;
60+ publicPath: string;
61+};
62+function Dl({ a }: { a: ArtifactEntry }) {
63+ return (
64+ <li className="flex items-center justify-between gap-3 py-1.5 text-sm">
65+ <a href={`${a.publicPath}?download=1`} className="font-mono text-xs break-all">
66+ {a.path}
67+ </a>
68+ <span className="subtle whitespace-nowrap text-xs">
69+ {fmtBytes(a.bytes)} · {shortHash(a.sha256, 8)}
70+ </span>
71+ </li>
72+ );
73+}
74+
75+export default async function PaperPage({ params }: { params: Promise<{ slug: string }> }) {
76+ const { slug } = await params;
77+ const p = await load(slug);
78+ if (!p) notFound();
79+ const base = process.env.APP_BASE_URL ?? "http://localhost:8470";
80+ const v = p.version;
81+ const meta = v.metadata as Record<string, unknown>;
82+ const withdrawn = meta.artifactsWithdrawn === true;
83+ const set = withdrawn ? [] : v.artifactSet;
84+ const url = `${base}/research/${p.paper.slug}`;
85+ const pub = new Date(v.publishedAt);
86+ const cite = {
87+ publicId: p.paper.publicId,
88+ title: v.title,
89+ year: pub.getUTCFullYear(),
90+ month: pub.getUTCMonth() + 1,
91+ versionNo: v.versionNo,
92+ url,
93+ keywords: v.keywords,
94+ abstract: v.abstract,
95+ };
96+ const figures = set.filter((a) => a.kind === "figure" && /\.(png|svg)$/i.test(a.path));
97+ const pdfFigures = set.filter((a) => a.kind === "figure" && /\.pdf$/i.test(a.path));
98+ const tex = set.filter((a) => a.kind === "manuscript" && /\.(tex|bib|cls|sty)$/i.test(a.path));
99+ const code = set.filter((a) => a.kind === "code");
100+ const data = set.filter((a) => a.kind === "data");
101+ const results = set.filter((a) => a.kind === "result" || a.kind === "table");
102+ const prov = (v.provenance ?? {}) as Record<string, unknown>;
103+ const qa = (v.qaSummary ?? {}) as {
104+ gates?: Array<{ gate: string; status: string; severity: string; message: string }>;
105+ warnings?: string[];
106+ policyVersion?: string;
107+ evaluatedAt?: string;
108+ };
109+ let bib: ReturnType<typeof parseBib> = [];
110+ const bibEntry = set.find((a) => a.path === "sources/references.bib");
111+ if (bibEntry) {
112+ try {
113+ bib = parseBib(
114+ (await storage().get(publicArtifactKey(p.paper.publicId, v.versionNo, bibEntry.path))).toString(
115+ "utf8",
116+ ),
117+ );
118+ } catch {
119+ bib = [];
120+ }
121+ }
122+ const jsonLd = {
123+ "@context": "https://schema.org",
124+ "@type": "ScholarlyArticle",
125+ headline: v.title,
126+ name: v.title,
127+ abstract: v.abstract,
128+ keywords: v.keywords.join(", "),
129+ datePublished: v.publishedAt.toISOString(),
130+ dateModified: p.paper.updatedAt.toISOString(),
131+ author: { "@type": "Organization", name: "AutoScholar (AI-generated research)", url: base },
132+ publisher: { "@type": "Organization", name: "AutoScholar.ai", url: base },
133+ identifier: p.paper.publicId,
134+ url,
135+ isAccessibleForFree: true,
136+ ...(set.length
137+ ? {
138+ encoding: {
139+ "@type": "MediaObject",
140+ contentUrl: `${base}${v.pdfPublicPath}`,
141+ encodingFormat: "application/pdf",
142+ },
143+ }
144+ : {}),
145+ about: disciplineLabel(p.paper.discipline),
146+ version: v.versionNo,
147+ ...(p.retraction ? { creativeWorkStatus: "Retracted" } : {}),
148+ };
149+ return (
150+ <article className="space-y-10">
151+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD built from server data */}
152+ <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
153+ {p.retraction && (
154+ <div
155+ role="alert"
156+ className="rounded-md border p-4"
157+ style={{
158+ borderColor: "var(--color-danger-500)",
159+ background: "color-mix(in oklab, var(--color-danger-500) 8%, transparent)",
160+ }}
161+ >
162+ <p className="font-semibold">
163+ This working paper was retracted on {fmtDate(p.retraction.retractedAt)}.
164+ </p>
165+ <p className="mt-1 text-sm">{p.retraction.notice}</p>
166+ {withdrawn && (
167+ <p className="muted mt-1 text-sm">
168+ Its artifacts have been withdrawn from delivery. This page remains as a permanent record.
169+ </p>
170+ )}
171+ </div>
172+ )}
173+ <header className="grid gap-6 md:grid-cols-12">
174+ <div className="md:col-span-8">
175+ <div className="flex flex-wrap items-center gap-2 text-xs">
176+ <span className="font-mono">{p.paper.publicId}</span>
177+ <span className="subtle">·</span>
178+ <span>Version {v.versionNo}</span>
179+ <StatusBadge status={p.paper.status} />
180+ {p.paper.synthetic && <span className="badge">staging sample — not a real paper</span>}
181+ </div>
182+ <h1 className="serif mt-3 text-3xl font-semibold leading-tight sm:text-4xl">{v.title}</h1>
183+ <p className="mt-4 text-sm">
184+ <span className="font-medium">{t("byline")}</span>
185+ <span className="muted"> · Research computed on MacLustr</span>
186+ </p>
187+ <dl className="muted mt-4 grid grid-cols-2 gap-x-6 gap-y-1 text-sm sm:grid-cols-4">
188+ <div>
189+ <dt className="label">Released</dt>
190+ <dd>{fmtDate(p.paper.firstPublishedAt)}</dd>
191+ </div>
192+ <div>
193+ <dt className="label">Revised</dt>
194+ <dd>{fmtDate(v.publishedAt)}</dd>
195+ </div>
196+ <div>
197+ <dt className="label">Subject</dt>
198+ <dd>
199+ <Link href={`/research?discipline=${p.paper.discipline}`}>
200+ {disciplineLabel(p.paper.discipline)}
201+ </Link>
202+ </dd>
203+ </div>
204+ <div>
205+ <dt className="label">Methodology</dt>
206+ <dd>{modeLabel(p.paper.researchMode)}</dd>
207+ </div>
208+ </dl>
209+ <ul className="mt-4 flex flex-wrap gap-2" aria-label="Keywords">
210+ {v.keywords.map((k) => (
211+ <li key={k}>
212+ <Link href={`/research?q=${encodeURIComponent(k)}`} className="badge no-underline">
213+ {k}
214+ </Link>
215+ </li>
216+ ))}
217+ </ul>
218+ </div>
219+ <aside className="surface p-4 text-sm md:col-span-4">
220+ <p className="label mb-2">Downloads</p>
221+ {withdrawn ? (
222+ <p className="muted">Artifacts withdrawn.</p>
223+ ) : (
224+ <ul className="space-y-2">
225+ <li>
226+ <a className="btn btn-primary w-full" href={`${v.pdfPublicPath}?download=1`}>
227+ PDF
228+ </a>
229+ </li>
230+ {tex.length > 0 && (
231+ <li>
232+ <a
233+ className="btn w-full"
234+ href={`${set.find((a) => a.path === "manuscript/main.tex")?.publicPath ?? tex[0]!.publicPath}?download=1`}
235+ >
236+ LaTeX source
237+ </a>
238+ </li>
239+ )}
240+ {code.length > 0 && (
241+ <li>
242+ <a className="btn w-full" href="#code">
243+ Code ({code.length} files)
244+ </a>
245+ </li>
246+ )}
247+ {data.length > 0 && (
248+ <li>
249+ <a className="btn w-full" href="#data">
250+ Data ({data.length} files)
251+ </a>
252+ </li>
253+ )}
254+ </ul>
255+ )}
256+ <div className="mt-4">
257+ <Disclosure compact />
258+ </div>
259+ </aside>
260+ </header>
261+
262+ <section aria-labelledby="abstract" className="grid gap-8 md:grid-cols-12">
263+ <div className="md:col-span-8">
264+ <h2 id="abstract" className="serif text-xl font-semibold">
265+ Abstract
266+ </h2>
267+ <p className="prose-paper mt-3">{v.abstract}</p>
268+ {Array.isArray(meta.contributions) && (meta.contributions as string[]).length > 0 && (
269+ <>
270+ <h2 className="serif mt-8 text-xl font-semibold">Key contributions</h2>
271+ <ol className="prose-paper mt-3 list-decimal pl-5">
272+ {(meta.contributions as string[]).map((c) => (
273+ <li key={c}>{c}</li>
274+ ))}
275+ </ol>
276+ </>
277+ )}
278+ {typeof meta.methods === "string" && meta.methods && (
279+ <>
280+ <h2 className="serif mt-8 text-xl font-semibold">Methods</h2>
281+ <p className="prose-paper mt-3">{meta.methods}</p>
282+ </>
283+ )}
284+ {typeof meta.mainResults === "string" && meta.mainResults && (
285+ <>
286+ <h2 className="serif mt-8 text-xl font-semibold">Main results</h2>
287+ <p className="prose-paper mt-3">{meta.mainResults}</p>
288+ </>
289+ )}
290+ {typeof meta.limitations === "string" && meta.limitations && (
291+ <>
292+ <h2 className="serif mt-8 text-xl font-semibold">Limitations</h2>
293+ <p className="prose-paper mt-3">{meta.limitations}</p>
294+ </>
295+ )}
296+ </div>
297+ <div className="space-y-4 md:col-span-4">
298+ <div className="surface p-4 text-sm">
299+ <p className="label mb-2">Research status</p>
300+ <ul className="space-y-1">
301+ <li>AutoScholar working paper (preprint)</li>
302+ <li>Produced autonomously by an AI research workflow</li>
303+ <li>
304+ Automated critical review; <strong>not</strong> human peer review
305+ </li>
306+ <li>
307+ Deterministic quality gates:{" "}
308+ {qa.gates
309+ ? `${qa.gates.filter((g) => g.status === "pass").length}/${qa.gates.filter((g) => g.status !== "skip").length} passed`
310+ : "see provenance"}
311+ </li>
312+ <li>DOI: none (not registered)</li>
313+ </ul>
314+ </div>
315+ {p.summary.topics.length > 0 && (
316+ <div className="surface p-4 text-sm">
317+ <p className="label mb-2">Topics</p>
318+ <ul className="flex flex-wrap gap-2">
319+ {p.summary.topics.map((x) => (
320+ <li key={x.slug}>
321+ <Link href={`/topics/${x.slug}`} className="badge no-underline">
322+ {x.name}
323+ </Link>
324+ </li>
325+ ))}
326+ </ul>
327+ </div>
328+ )}
329+ </div>
330+ </section>
331+
332+ {!withdrawn && (
333+ <section aria-labelledby="pdf">
334+ <h2 id="pdf" className="serif text-xl font-semibold">
335+ Read the paper
336+ </h2>
337+ <div className="mt-3">
338+ <PdfViewer
339+ src={v.pdfPublicPath}
340+ title={`${p.paper.publicId} v${v.versionNo} — ${v.title}`}
341+ downloadHref={`${v.pdfPublicPath}?download=1`}
342+ />
343+ </div>
344+ </section>
345+ )}
346+
347+ {(figures.length > 0 || pdfFigures.length > 0) && (
348+ <section aria-labelledby="figures">
349+ <h2 id="figures" className="serif text-xl font-semibold">
350+ Figures
351+ </h2>
352+ <ul className="mt-3 grid gap-4 sm:grid-cols-2">
353+ {figures.map((f) => (
354+ <li key={f.path} className="surface p-3">
355+ {/* biome-ignore lint/performance/noImgElement: artifact images are served immutable from this origin */}
356+ <img
357+ src={f.publicPath}
358+ alt={f.path.split("/").pop() ?? "figure"}
359+ className="w-full"
360+ loading="lazy"
361+ />
362+ <p className="subtle mt-2 font-mono text-xs">{f.path.replace("results/figures/", "")}</p>
363+ </li>
364+ ))}
365+ {pdfFigures.map((f) => (
366+ <li key={f.path} className="surface p-3 text-sm">
367+ <a href={`${f.publicPath}?download=1`}>{f.path.replace("results/figures/", "")}</a>{" "}
368+ <span className="subtle">(vector PDF, {fmtBytes(f.bytes)})</span>
369+ </li>
370+ ))}
371+ </ul>
372+ </section>
373+ )}
374+
375+ {bib.length > 0 && (
376+ <section aria-labelledby="refs">
377+ <h2 id="refs" className="serif text-xl font-semibold">
378+ References ({bib.length})
379+ </h2>
380+ <p className="muted mt-1 text-xs">
381+ Every reference was verified against a scholarly registry before publication; identifiers link to
382+ the source of record.
383+ </p>
384+ <ol className="mt-3 space-y-2 text-sm">
385+ {bib.map((b) => {
386+ const f = b.fields;
387+ const href = f.doi
388+ ? `https://doi.org/${f.doi.replace(/^https?:\/\/doi\.org\//, "")}`
389+ : (f.url ?? (f.eprint ? `https://arxiv.org/abs/${f.eprint}` : undefined));
390+ return (
391+ <li key={b.key} id={`ref-${b.key}`} className="border-b pb-2 hairline">
392+ <span>{f.author ? `${f.author.replace(/\s+and\s+/g, ", ")}. ` : ""}</span>
393+ {f.year && <span>({f.year}). </span>}
394+ <span className="italic">{f.title?.replace(/[{}]/g, "")}</span>
395+ {f.journal && <span>. {f.journal}</span>}
396+ {f.booktitle && <span>. In {f.booktitle}</span>}
397+ {f.volume && <span>, {f.volume}</span>}
398+ {f.pages && <span>, {f.pages}</span>}.{" "}
399+ {href && (
400+ <a href={href} rel="noopener noreferrer" className="font-mono text-xs break-all">
401+ {f.doi ? `doi:${f.doi}` : href}
402+ </a>
403+ )}
404+ </li>
405+ );
406+ })}
407+ </ol>
408+ </section>
409+ )}
410+
411+ {(code.length > 0 || data.length > 0 || results.length > 0) && (
412+ <section aria-labelledby="repro" className="grid gap-6 md:grid-cols-3">
413+ <h2 id="repro" className="serif text-xl font-semibold md:col-span-3">
414+ Code, data and results
415+ </h2>
416+ {code.length > 0 && (
417+ <div id="code" className="surface p-4">
418+ <p className="label mb-1">Code</p>
419+ <ul>
420+ {code.map((a) => (
421+ <Dl key={a.path} a={a} />
422+ ))}
423+ </ul>
424+ </div>
425+ )}
426+ {data.length > 0 && (
427+ <div id="data" className="surface p-4">
428+ <p className="label mb-1">Data</p>
429+ <ul>
430+ {data.map((a) => (
431+ <Dl key={a.path} a={a} />
432+ ))}
433+ </ul>
434+ </div>
435+ )}
436+ {results.length > 0 && (
437+ <div className="surface p-4">
438+ <p className="label mb-1">Results and tables</p>
439+ <ul>
440+ {results.map((a) => (
441+ <Dl key={a.path} a={a} />
442+ ))}
443+ </ul>
444+ </div>
445+ )}
446+ </section>
447+ )}
448+
449+ <section aria-labelledby="reproducibility" className="grid gap-6 md:grid-cols-2">
450+ <div>
451+ <h2 id="reproducibility" className="serif text-xl font-semibold">
452+ Reproducibility
453+ </h2>
454+ <dl className="mt-3 grid grid-cols-[max-content_1fr] gap-x-4 gap-y-1 text-sm">
455+ <dt className="label">Data availability</dt>
456+ <dd>{(meta.dataAvailability as string) || "Not applicable or not stated."}</dd>
457+ <dt className="label">Code availability</dt>
458+ <dd>{(meta.codeAvailability as string) || "Not applicable or not stated."}</dd>
459+ <dt className="label">Ethics</dt>
460+ <dd>
461+ {(meta.ethics as string) ||
462+ "No restricted activities; public data, mathematics or simulations only."}
463+ </dd>
464+ <dt className="label">Toolchain</dt>
465+ <dd className="font-mono text-xs">
466+ {prov.toolchain && Object.keys(prov.toolchain as object).length
467+ ? Object.entries(prov.toolchain as Record<string, string>)
468+ .map(([k, val]) => `${k} ${val}`)
469+ .join(" · ")
470+ : "not recorded"}
471+ </dd>
472+ <dt className="label">LaTeX source</dt>
473+ <dd>
474+ {tex.length
475+ ? "Released; the PDF was rebuilt from it in a clean environment during verification."
476+ : "Not released."}
477+ </dd>
478+ <dt className="label">Verified</dt>
479+ <dd>
480+ {qa.evaluatedAt ? `${fmtDate(qa.evaluatedAt)} under quality policy ${qa.policyVersion}` : "—"}
481+ </dd>
482+ </dl>
483+ {qa.warnings && qa.warnings.length > 0 && (
484+ <details className="mt-3 text-sm">
485+ <summary className="cursor-pointer">
486+ Non-blocking warnings recorded at verification ({qa.warnings.length})
487+ </summary>
488+ <ul className="muted mt-2 list-disc pl-5">
489+ {qa.warnings.map((w) => (
490+ <li key={w}>{w}</li>
491+ ))}
492+ </ul>
493+ </details>
494+ )}
495+ </div>
496+ <div>
497+ <h2 className="serif text-xl font-semibold">Provenance</h2>
498+ <dl className="mt-3 grid grid-cols-[max-content_1fr] gap-x-4 gap-y-1 text-sm">
499+ <dt className="label">Model</dt>
500+ <dd className="font-mono text-xs">{String(prov.model ?? "—")}</dd>
501+ <dt className="label">Reviewer model</dt>
502+ <dd className="font-mono text-xs">{String(prov.reviewerModel ?? "—")}</dd>
503+ <dt className="label">Instruction version</dt>
504+ <dd className="font-mono text-xs">
505+ v{String(prov.instructionVersion ?? "?")} · {shortHash(prov.instructionSha256 as string)}
506+ </dd>
507+ <dt className="label">Manifest digest</dt>
508+ <dd className="font-mono text-xs">{shortHash(prov.manifestSha256 as string)}</dd>
509+ <dt className="label">Execution</dt>
510+ <dd className="text-xs">
511+ {prov.startedAt
512+ ? `${fmtDate(prov.startedAt as string)} → ${prov.completedAt ? fmtDate(prov.completedAt as string) : "?"}`
513+ : "—"}{" "}
514+ · {String(prov.infrastructure ?? "MacLustr")}
515+ </dd>
516+ <dt className="label">Quality gates</dt>
517+ <dd>
518+ {qa.gates && qa.gates.length > 0 ? (
519+ <details className="text-xs">
520+ <summary className="cursor-pointer">{qa.gates.length} gates evaluated</summary>
521+ <ul className="mt-1 space-y-0.5">
522+ {qa.gates.map((g) => (
523+ <li key={g.gate}>
524+ <StatusBadge status={g.status} /> <span className="font-mono">{g.gate}</span>{" "}
525+ <span className="muted">— {g.message}</span>
526+ </li>
527+ ))}
528+ </ul>
529+ </details>
530+ ) : (
531+ "—"
532+ )}
533+ </dd>
534+ </dl>
535+ <p className="subtle mt-2 text-xs">
536+ Artifact digests (SHA-256) are listed with each download; the full list is in
537+ provenance/checksums.sha256.
538+ </p>
539+ </div>
540+ </section>
541+
542+ <section aria-labelledby="cite">
543+ <h2 id="cite" className="serif text-xl font-semibold">
544+ Cite this working paper
545+ </h2>
546+ <div className="mt-3">
547+ <Tabs
548+ tabs={[
549+ {
550+ label: "BibTeX",
551+ content: (
552+ <div>
553+ <pre className="surface overflow-auto p-4 text-xs">{bibtexFor(cite)}</pre>
554+ <div className="mt-2 flex gap-2">
555+ <CopyButton text={bibtexFor(cite)} />
556+ <a className="btn btn-sm" href={`/research/${p.paper.slug}/cite.bib`}>
557+ Download .bib
558+ </a>
559+ </div>
560+ </div>
561+ ),
562+ },
563+ {
564+ label: "RIS",
565+ content: (
566+ <div>
567+ <pre className="surface overflow-auto p-4 text-xs">{risFor(cite)}</pre>
568+ <div className="mt-2 flex gap-2">
569+ <CopyButton text={risFor(cite)} />
570+ <a className="btn btn-sm" href={`/research/${p.paper.slug}/cite.ris`}>
571+ Download .ris
572+ </a>
573+ </div>
574+ </div>
575+ ),
576+ },
577+ {
578+ label: "Plain text",
579+ content: (
580+ <div>
581+ <p className="surface p-4 text-sm">{plainCitationFor(cite)}</p>
582+ <div className="mt-2 flex gap-2">
583+ <CopyButton text={plainCitationFor(cite)} />
584+ <a className="btn btn-sm" href={`/research/${p.paper.slug}/cite.txt`}>
585+ Download .txt
586+ </a>
587+ </div>
588+ </div>
589+ ),
590+ },
591+ ]}
592+ />
593+ </div>
594+ </section>
595+
596+ <section aria-labelledby="history" className="grid gap-6 md:grid-cols-2">
597+ <div>
598+ <h2 id="history" className="serif text-xl font-semibold">
599+ Version history
600+ </h2>
601+ <ol className="mt-3 space-y-2 text-sm">
602+ {p.versions.map((x) => (
603+ <li key={x.id} className="surface flex items-start justify-between gap-3 p-3">
604+ <div>
605+ <span className="font-medium">Version {x.versionNo}</span>{" "}
606+ <span className="muted">· {fmtDate(x.publishedAt)}</span>
607+ <p className="muted mt-1">{x.changelog}</p>
608+ </div>
609+ {!withdrawn && (
610+ <a className="btn btn-sm" href={`${x.pdfPublicPath}?download=1`}>
611+ PDF v{x.versionNo}
612+ </a>
613+ )}
614+ </li>
615+ ))}
616+ </ol>
617+ </div>
618+ <div>
619+ <h2 className="serif text-xl font-semibold">Corrections and notices</h2>
620+ {p.corrections.length === 0 && !p.retraction ? (
621+ <p className="muted mt-3 text-sm">No corrections have been issued.</p>
622+ ) : (
623+ <ul className="mt-3 space-y-2 text-sm">
624+ {p.corrections.map((c) => (
625+ <li key={c.id} className="surface p-3">
626+ <span className="font-medium">
627+ Correction v{c.fromVersionNo} → v{c.toVersionNo}
628+ </span>{" "}
629+ <span className="muted">· {fmtDate(c.createdAt)}</span>
630+ <p className="mt-1">{c.notice}</p>
631+ </li>
632+ ))}
633+ {p.retraction && (
634+ <li className="surface p-3">
635+ <span className="font-medium">Retraction</span>{" "}
636+ <span className="muted">· {fmtDate(p.retraction.retractedAt)}</span>
637+ <p className="mt-1">{p.retraction.notice}</p>
638+ </li>
639+ )}
640+ </ul>
641+ )}
642+ </div>
643+ </section>
644+
645+ {p.related.length > 0 && (
646+ <section aria-labelledby="related">
647+ <h2 id="related" className="serif text-xl font-semibold">
648+ Related working papers
649+ </h2>
650+ <p className="muted mt-1 text-xs">
651+ Selected by shared discipline or keywords recorded in verified metadata.
652+ </p>
653+ <ul className="mt-3 grid gap-4 md:grid-cols-2">
654+ {p.related.map((r) => (
655+ <li key={r.publicId}>
656+ <PaperCard p={r} />
657+ </li>
658+ ))}
659+ </ul>
660+ </section>
661+ )}
662+ <Disclosure />
663+ </article>
664+ );
665+}
added apps/web/src/app/(public)/research/page.tsx +82 −0
@@ -0,0 +1,82 @@
1+import { listSeries, searchPapers } from "@autoscholar/core";
2+import { PublicSearchQuery } from "@autoscholar/schemas";
3+import type { Metadata } from "next";
4+import { Disclosure } from "@/components/Disclosure";
5+import { EmptyState } from "@/components/EmptyState";
6+import { Pagination } from "@/components/Pagination";
7+import { PaperCard } from "@/components/PaperCard";
8+import { SearchFilters } from "@/components/SearchFilters";
9+import { t } from "@/lib/i18n";
10+import { db } from "@/lib/server/db";
11+
12+export const dynamic = "force-dynamic";
13+export const metadata: Metadata = {
14+ title: "Research library",
15+ description: "Searchable library of AutoScholar working papers.",
16+ alternates: { canonical: "/research" },
17+};
18+
19+export default async function ResearchPage({
20+ searchParams,
21+}: {
22+ searchParams: Promise<Record<string, string | string[] | undefined>>;
23+}) {
24+ const sp = await searchParams;
25+ const raw: Record<string, string | undefined> = {};
26+ for (const [k, v] of Object.entries(sp)) raw[k] = Array.isArray(v) ? v[0] : v;
27+ for (const k of Object.keys(raw)) if (raw[k] === "") delete raw[k];
28+ const parsed = PublicSearchQuery.safeParse(raw);
29+ const q = parsed.success ? parsed.data : PublicSearchQuery.parse({});
30+ const d = db();
31+ const res = await searchPapers(d, q);
32+ const series = await listSeries(d);
33+ const hrefFor = (p: number) => {
34+ const u = new URLSearchParams();
35+ for (const [k, v] of Object.entries(raw)) if (v && k !== "page") u.set(k, v);
36+ u.set("page", String(p));
37+ return `/research?${u.toString()}`;
38+ };
39+ const anyFilter = Object.keys(raw).some((k) => !["page", "sort", "pageSize"].includes(k));
40+ return (
41+ <div className="space-y-6">
42+ <header>
43+ <p className="eyebrow">Library</p>
44+ <h1 className="serif mt-1 text-3xl font-semibold">Working papers</h1>
45+ <p className="muted mt-2 max-w-2xl text-sm">
46+ Search titles, abstracts, keywords and verified metadata. Filter by discipline, methodology, series,
47+ availability of code and data, and publication status.
48+ </p>
49+ </header>
50+ <SearchFilters q={raw} series={series} />
51+ {!parsed.success && (
52+ <p role="alert" className="text-sm" style={{ color: "var(--color-danger-500)" }}>
53+ Some filters were invalid and have been ignored.
54+ </p>
55+ )}
56+ <div className="flex items-center justify-between text-sm">
57+ <p className="muted" aria-live="polite">
58+ {res.total === 0 ? "No results" : `${res.total} ${res.total === 1 ? "paper" : "papers"}`}
59+ {raw.q ? ` for “${raw.q}”` : ""}
60+ </p>
61+ <Pagination page={res.page} pageSize={res.pageSize} total={res.total} hrefFor={hrefFor} />
62+ </div>
63+ {res.items.length === 0 ? (
64+ <EmptyState title={anyFilter ? t("search.empty") : "No working papers yet."}>
65+ {anyFilter ? <a href="/research">Clear all filters</a> : t("library.empty")}
66+ </EmptyState>
67+ ) : (
68+ <ul className="grid gap-4 md:grid-cols-2">
69+ {res.items.map((p) => (
70+ <li key={p.publicId}>
71+ <PaperCard p={p} />
72+ </li>
73+ ))}
74+ </ul>
75+ )}
76+ <div className="flex justify-end">
77+ <Pagination page={res.page} pageSize={res.pageSize} total={res.total} hrefFor={hrefFor} />
78+ </div>
79+ <Disclosure compact />
80+ </div>
81+ );
82+}
added apps/web/src/app/(public)/series/page.tsx +44 −0
@@ -0,0 +1,44 @@
1+import { listSeries } from "@autoscholar/core";
2+import type { Metadata } from "next";
3+import Link from "next/link";
4+import { EmptyState } from "@/components/EmptyState";
5+import { db } from "@/lib/server/db";
6+
7+export const dynamic = "force-dynamic";
8+export const metadata: Metadata = { title: "Working-paper series", alternates: { canonical: "/series" } };
9+
10+export default async function SeriesPage() {
11+ const rows = await listSeries(db());
12+ return (
13+ <div className="space-y-8">
14+ <header>
15+ <p className="eyebrow">Collections</p>
16+ <h1 className="serif mt-1 text-3xl font-semibold">Working-paper series</h1>
17+ <p className="muted mt-2 max-w-2xl text-sm">
18+ Papers are numbered in a stable series (AS-WP-YEAR-NNNN). Numbers are allocated inside the
19+ publication transaction and never reused, even after a retraction.
20+ </p>
21+ </header>
22+ {rows.length === 0 ? (
23+ <EmptyState title="No series configured." />
24+ ) : (
25+ <ul className="grid gap-3 md:grid-cols-2">
26+ {rows.map((s) => (
27+ <li key={s.id} className="surface p-5">
28+ <Link
29+ href={`/research?series=${s.slug}`}
30+ className="serif text-lg font-semibold no-underline hover:underline"
31+ >
32+ {s.name}
33+ </Link>
34+ <p className="subtle mt-1 font-mono text-xs">
35+ {s.paperCount} {s.paperCount === 1 ? "paper" : "papers"}
36+ </p>
37+ {s.description && <p className="muted mt-2 text-sm">{s.description}</p>}
38+ </li>
39+ ))}
40+ </ul>
41+ )}
42+ </div>
43+ );
44+}
added apps/web/src/app/(public)/topics/[slug]/page.tsx +102 −0
Large diff (102 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/(public)/topics/page.tsx +63 −0
Large diff (63 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/(public)/transparency/page.tsx +118 −0
Large diff (118 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/LogoutButton.tsx +25 −0
Large diff (25 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/layout.tsx +68 −0
Large diff (68 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/maclustr/WorkerActions.tsx +55 −0
Large diff (55 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/maclustr/page.tsx +106 −0
Large diff (106 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/manuscripts/page.tsx +179 −0
Large diff (179 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/new/NewResearchForm.tsx +482 −0
Large diff (482 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/new/page.tsx +49 −0
Large diff (49 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/page.tsx +148 −0
Large diff (148 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/projects/[id]/page.tsx +114 −0
Large diff (114 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/projects/page.tsx +54 −0
Large diff (54 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/publications/PublicationActions.tsx +227 −0
Large diff (227 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/publications/page.tsx +118 −0
Large diff (118 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/quality/[runId]/OverrideForm.tsx +43 −0
Large diff (43 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/quality/[runId]/page.tsx +183 −0
Large diff (183 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/quality/page.tsx +64 −0
Large diff (64 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/runs/[id]/LiveTimeline.tsx +118 −0
Large diff (118 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/runs/[id]/RunActions.tsx +79 −0
Large diff (79 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/runs/[id]/page.tsx +186 −0
Large diff (186 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/runs/page.tsx +98 −0
Large diff (98 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/settings/SettingsControls.tsx +91 −0
Large diff (91 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/settings/page.tsx +102 −0
Large diff (102 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/(console)/templates/page.tsx +70 −0
Large diff (70 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/change-password/page.tsx +98 −0
Large diff (98 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/layout.tsx +12 −0
Large diff (12 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/admin/login/page.tsx +76 −0
Large diff (76 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/audit/route.ts +14 −0
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/auth/change-password/route.ts +19 −0
Large diff (19 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/auth/login/route.ts +36 −0
Large diff (36 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/auth/logout/route.ts +17 −0
Large diff (17 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/auth/session/route.ts +16 −0
Large diff (16 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/overview/route.ts +30 −0
Large diff (30 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/projects/[id]/instructions/route.ts +64 −0
Large diff (64 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/projects/[id]/route.ts +12 −0
Large diff (12 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/projects/route.ts +19 −0
Large diff (19 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/publications/[id]/metadata/route.ts +23 −0
Large diff (23 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/publications/[id]/retract/route.ts +17 −0
Large diff (17 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/publications/[id]/withdraw/route.ts +14 −0
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/publications/route.ts +33 −0
Large diff (33 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/quality/[runId]/override/route.ts +83 −0
Large diff (83 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/quality/[runId]/route.ts +36 −0
Large diff (36 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/runs/[id]/artifacts/[...path]/route.ts +40 −0
Large diff (40 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/runs/[id]/cancel/route.ts +12 −0
Large diff (12 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/runs/[id]/events/route.ts +71 −0
Large diff (71 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/runs/[id]/retry/route.ts +12 −0
Large diff (12 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/runs/[id]/route.ts +12 −0
Large diff (12 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/runs/route.ts +30 −0
Large diff (30 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/settings/route.ts +46 −0
Large diff (46 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/templates/route.ts +11 −0
Large diff (11 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/workers/[id]/drain/route.ts +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/workers/[id]/quarantine/route.ts +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/admin/workers/route.ts +11 −0
Large diff (11 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/internal/workers/enroll/route.ts +22 −0
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/internal/workers/heartbeat/route.ts +13 −0
Large diff (13 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/internal/workers/lease/route.ts +13 −0
Large diff (13 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/internal/workers/runs/[id]/artifacts/route.ts +41 −0
Large diff (41 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/internal/workers/runs/[id]/complete/route.ts +14 −0
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/internal/workers/runs/[id]/events/route.ts +14 −0
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/internal/workers/runs/[id]/renew/route.ts +14 −0
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/internal/workers/runs/[id]/stage/route.ts +14 −0
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/public/papers/[slug]/route.ts +43 −0
Large diff (43 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/public/papers/route.ts +16 −0
Large diff (16 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/api/public/topics/route.ts +13 −0
Large diff (13 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/error.tsx +22 −0
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/feed.atom/route.ts +26 −0
Large diff (26 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/feed.xml/route.ts +22 −0
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/globals.css +282 −0
Large diff (282 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/layout.tsx +35 −0
Large diff (35 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/not-found.tsx +17 −0
Large diff (17 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/papers/[publicId]/[version]/[...path]/route.ts +58 −0
Large diff (58 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/robots.ts +10 −0
Large diff (10 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/app/sitemap.ts +39 −0
Large diff (39 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/CodeBlock.tsx +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/CopyButton.tsx +24 −0
Large diff (24 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/DataTable.tsx +48 −0
Large diff (48 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/Disclosure.tsx +23 −0
Large diff (23 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/EmptyState.tsx +10 −0
Large diff (10 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/MarkdownView.tsx +8 −0
Large diff (8 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/Pagination.tsx +45 −0
Large diff (45 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/PaperCard.tsx +36 −0
Large diff (36 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/PdfViewer.tsx +26 −0
Large diff (26 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/SearchFilters.tsx +124 −0
Large diff (124 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/SiteFooter.tsx +66 −0
Large diff (66 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/SiteHeader.tsx +49 −0
Large diff (49 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/StatusBadge.tsx +48 −0
Large diff (48 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/Tabs.tsx +50 −0
Large diff (50 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/ThemeScript.tsx +6 −0
Large diff (6 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/ThemeToggle.tsx +31 −0
Large diff (31 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/Toast.tsx +43 −0
Large diff (43 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/lib/client/api.ts +64 −0
Large diff (64 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/lib/fonts.ts +16 −0
Large diff (16 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/lib/format.ts +47 −0
Large diff (47 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/lib/i18n.ts +49 −0
Large diff (49 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/lib/server/api.ts +68 −0
Large diff (68 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/lib/server/db.ts +14 −0
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/lib/server/internal.ts +25 −0
Large diff (25 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/lib/server/ip.ts +64 −0
Large diff (64 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/lib/server/markdown.ts +53 −0
Large diff (53 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/lib/server/session.ts +71 −0
Large diff (71 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/tsconfig.json +28 −0
Large diff (28 lines) is not loaded by default to keep the page fast.Load diff
added biome.json +20 −0
Large diff (20 lines) is not loaded by default to keep the page fast.Load diff
added docs/DECISIONS.md +63 −0
Large diff (63 lines) is not loaded by default to keep the page fast.Load diff
added docs/INFRA_DISCOVERY.md +73 −0
Large diff (73 lines) is not loaded by default to keep the page fast.Load diff
added docs/OPERATIONS.md +70 −0
Large diff (70 lines) is not loaded by default to keep the page fast.Load diff
added docs/RESEARCH_INSTRUCTIONS_GUIDE.md +51 −0
Large diff (51 lines) is not loaded by default to keep the page fast.Load diff
added docs/SECURITY.md +39 −0
Large diff (39 lines) is not loaded by default to keep the page fast.Load diff
added package.json +37 −0
Large diff (37 lines) is not loaded by default to keep the page fast.Load diff
added packages/auth/package.json +21 −0
Large diff (21 lines) is not loaded by default to keep the page fast.Load diff
added packages/auth/src/auth.test.ts +45 −0
Large diff (45 lines) is not loaded by default to keep the page fast.Load diff
added packages/auth/src/index.ts +122 −0
Large diff (122 lines) is not loaded by default to keep the page fast.Load diff
added packages/auth/tsconfig.json +6 −0
Large diff (6 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/package.json +28 −0
Large diff (28 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/src/admin-auth.ts +271 −0
Large diff (271 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/src/audit.ts +34 −0
Large diff (34 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/src/citations.ts +52 −0
Large diff (52 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/src/env.ts +40 −0
Large diff (40 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/src/errors.ts +18 −0
Large diff (18 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/src/index.ts +11 −0
Large diff (11 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/src/papers.ts +239 −0
Large diff (239 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/src/projects.ts +135 −0
Large diff (135 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/src/runs.ts +393 −0
Large diff (393 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/src/settings.ts +43 −0
Large diff (43 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/src/templates.ts +83 −0
Large diff (83 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/src/workers.ts +647 −0
Large diff (647 lines) is not loaded by default to keep the page fast.Load diff
added packages/core/tsconfig.json +6 −0
Large diff (6 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/drizzle.config.ts +10 −0
Large diff (10 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/drizzle/0000_initial.sql +609 −0
Large diff (609 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/drizzle/meta/0000_snapshot.json +4393 −0
Large diff (4,394 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/drizzle/meta/_journal.json +13 −0
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/package.json +36 −0
Large diff (36 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/client.ts +35 −0
Large diff (35 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/index.ts +2 −0
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/migrate.ts +25 −0
Large diff (25 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/schema/auth.ts +93 −0
Large diff (93 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/schema/evidence.ts +141 −0
Large diff (141 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/schema/index.ts +9 −0
Large diff (9 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/schema/papers.ts +173 −0
Large diff (173 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/schema/projects.ts +68 −0
Large diff (68 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/schema/quality.ts +49 −0
Large diff (49 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/schema/runs.ts +163 −0
Large diff (163 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/schema/settings.ts +9 −0
Large diff (9 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/schema/usage.ts +47 −0
Large diff (47 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/schema/workers.ts +63 −0
Large diff (63 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/seed.ts +227 −0
Large diff (227 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/tsconfig.json +5 −0
Large diff (5 lines) is not loaded by default to keep the page fast.Load diff
added packages/maclustr-adapter/package.json +22 −0
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
added packages/maclustr-adapter/src/index.ts +170 −0
Large diff (170 lines) is not loaded by default to keep the page fast.Load diff
added packages/maclustr-adapter/tsconfig.json +6 −0
Large diff (6 lines) is not loaded by default to keep the page fast.Load diff
added packages/observability/package.json +21 −0
Large diff (21 lines) is not loaded by default to keep the page fast.Load diff
added packages/observability/src/index.ts +52 −0
Large diff (52 lines) is not loaded by default to keep the page fast.Load diff
added packages/observability/tsconfig.json +6 −0
Large diff (6 lines) is not loaded by default to keep the page fast.Load diff
added packages/research-contract/package.json +22 −0
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
added packages/research-contract/src/contract.test.ts +50 −0
Large diff (50 lines) is not loaded by default to keep the page fast.Load diff
added packages/research-contract/src/contract.ts +58 −0
Large diff (58 lines) is not loaded by default to keep the page fast.Load diff
added packages/research-contract/src/index.ts +3 −0
Large diff (3 lines) is not loaded by default to keep the page fast.Load diff
added packages/research-contract/src/instructions.ts +124 −0
Large diff (124 lines) is not loaded by default to keep the page fast.Load diff
added packages/research-contract/src/profiles.ts +109 −0
Large diff (109 lines) is not loaded by default to keep the page fast.Load diff
added packages/research-contract/tsconfig.json +6 −0
Large diff (6 lines) is not loaded by default to keep the page fast.Load diff
added packages/schemas/package.json +21 −0
Large diff (21 lines) is not loaded by default to keep the page fast.Load diff
added packages/schemas/src/admin.ts +28 −0
Large diff (28 lines) is not loaded by default to keep the page fast.Load diff
added packages/schemas/src/artifacts.ts +190 −0
Large diff (190 lines) is not loaded by default to keep the page fast.Load diff
added packages/schemas/src/events.ts +24 −0
Large diff (24 lines) is not loaded by default to keep the page fast.Load diff
added packages/schemas/src/index.ts +8 −0
Large diff (8 lines) is not loaded by default to keep the page fast.Load diff
added packages/schemas/src/modes.ts +73 −0
Large diff (73 lines) is not loaded by default to keep the page fast.Load diff
added packages/schemas/src/papers.ts +41 −0
Large diff (41 lines) is not loaded by default to keep the page fast.Load diff
added packages/schemas/src/quality.ts +62 −0
Large diff (62 lines) is not loaded by default to keep the page fast.Load diff
added packages/schemas/src/run.ts +129 −0
Large diff (129 lines) is not loaded by default to keep the page fast.Load diff
added packages/schemas/src/worker-protocol.ts +114 −0
Large diff (114 lines) is not loaded by default to keep the page fast.Load diff
added packages/schemas/tsconfig.json +5 −0
Large diff (5 lines) is not loaded by default to keep the page fast.Load diff
added packages/storage/package.json +19 −0
Large diff (19 lines) is not loaded by default to keep the page fast.Load diff
added packages/storage/src/index.ts +185 −0
Large diff (185 lines) is not loaded by default to keep the page fast.Load diff
added packages/storage/src/storage.test.ts +46 −0
Large diff (46 lines) is not loaded by default to keep the page fast.Load diff
added packages/storage/tsconfig.json +6 −0
Large diff (6 lines) is not loaded by default to keep the page fast.Load diff
added pnpm-lock.yaml +2463 −0
Large diff (2,463 lines) is not loaded by default to keep the page fast.Load diff

Diff truncated — file too large.

Menu

Theme