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