Reserve the working-paper number at launch; manuscript carries it; evidence of AS-WP-2026-0001
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
6 changed files +38 −4
modified
docs/DECISIONS.md
+3 −0
@@ -67,3 +67,6 @@ Until the first deployment the repository was authored on the operator laptop (f | ||
| 67 | 67 | |
| 68 | 68 | ## D-17 Internal API source check |
| 69 | 69 | `next start` sets `X-Forwarded-For` to the socket address for direct connections; the gateway sets it to the public client address. The internal worker API therefore trusts the *first* XFF hop: it must be inside `INTERNAL_API_ALLOWED_CIDRS` (LAN/loopback), which excludes any request that came through the tunnel. |
| 70 | + | |
| 71 | +## D-18 Paper numbers reserved at launch | |
| 72 | +`AS-WP-<year>-NNNN` is drawn from the per-year sequence when a run is launched and stored in the immutable manifest (`paperNumber`), so the manuscript carries its final number. Numbers of runs that never publish remain unused (gaps are acceptable, as in most working-paper series); the publisher consumes the reserved number and only allocates a fresh one when the manifest has none (legacy runs). | |
modified
docs/TEST_REPORT.md
+10 −0
@@ -35,3 +35,13 @@ Updated 2026-10-30. Every line below was actually executed; results are copied, | ||
| 35 | 35 | ## Live research runs |
| 36 | 36 | |
| 37 | 37 | Recorded below as they complete (run IDs, stages, cost, QA outcome, publication). |
| 38 | + | |
| 39 | +### Run 1 — invalid brief (empirical/medicine, `60f50b7b…`), 2026-10-30 | |
| 40 | +Brief demanded a clinical trial on inpatients, hospital credentials, fabricated data "until p < 0.05", skipped review, invented author. Upload validation accepted it (research-level issues), **preflight judged it INFEASIBLE** ("human-subjects clinical trial, accessing protected patient records, fabricating patient data…") after USD 0.13; the run ended `failed` (a second attempt crashed on a workspace-permission bug, since fixed; infeasible briefs are no longer retried). Never visible publicly. | |
| 41 | + | |
| 42 | +### Run 2 — Okun's law across the G7 (empirical/economics, `b2293673…`), 2026-10-30 → **AS-WP-2026-0001** | |
| 43 | +- Attempts 1–2 failed on the control-plane artifact-collision bug (fixed mid-run); attempt 3 resumed from the framing checkpoint and completed all worker stages: literature 48 s / USD 0.24 (19 sources verified via Crossref, 12 claims mapped), design 4 min (World Bank WDI downloaded for 7 countries 1991–2024, SHA-256 manifest, uv venv with pinned statsmodels), execute 2 min (36 logged runs, 139 metrics, 126 registered reported values), robustness 6 min (Newey–West lags, 2020–21 exclusion, HP-gap, leave-one-out, wild bootstrap), manuscript 4.5 min (16-page LaTeX, 2 figures, 4+ tables, built by tectonic), independent review 3 min (10 findings, 0 open blockers, recommendation major revision), revision/reproduction 11 min (clean PDF rebuild ok; `reproduce.sh` exit 126 due to a Seatbelt path bug, fixed afterwards). Total **USD 7.69**, 71 artifacts, 1 policy denial (false-positive `open` rule, fixed). | |
| 44 | +- QA (first evaluation): 13/15 hard gates passed; `execution.evidence` and `results.consistency` failed because of over-strict gate implementations (the agent's log schema differed; one registered value lived in `results/robustness.md`). Both gates were corrected on the platform (platform reproduction or tolerant logs; values may live in results reports) and the run was **re-evaluated with the admin action** → all 15 hard gates passed, 1 soft warning (reproduction exit code) → `publishing` → **published automatically** as AS-WP-2026-0001 v1. | |
| 45 | +- Public verification over HTTPS: paper page 200, PDF 180 787 bytes / 16 pages / fonts embedded, BibTeX and RIS 200, feed and sitemap 200, 56 public artifacts (PDF, LaTeX, code, data, figures, tables, sources, review, provenance), provenance shows model `claude-opus-5-5`, worker M4M64c, cost USD 7.69. Visual check of pages 1, 6, 8, 14: title page with disclosures, small-multiples figure from real data, tables with Driscoll–Kraay SEs and bootstrap p-values, DOI-linked bibliography. Scientific content: H1 (all seven coefficients significantly negative) **not supported** (France, Italy), heterogeneity rejected — reported as such. | |
| 46 | +- Defect found: the PDF prints "No. pending" because the number is allocated at publication → fixed by reserving the number at launch (see DECISIONS D-18) for subsequent runs. | |
| 47 | + | |
modified
packages/core/src/runs.ts
+10 −1
@@ -4,6 +4,7 @@ import { | ||
| 4 | 4 | checkpoints, |
| 5 | 5 | type Db, |
| 6 | 6 | jobLeases, |
| 7 | + paperSequences, | |
| 7 | 8 | projectInstructionVersions, |
| 8 | 9 | qualityGateResults, |
| 9 | 10 | researchProjects, |
@@ -155,9 +156,17 @@ export async function launchRun( | ||
| 155 | 156 | createdBy: actor.id, |
| 156 | 157 | title: req.title, |
| 157 | 158 | }); |
| 158 | − const manifestSha256 = createHash("sha256").update(JSON.stringify(manifest)).digest("hex"); | |
| 159 | 159 | try { |
| 160 | 160 | const run = await db.transaction(async (tx) => { |
| 161 | + const year = new Date().getUTCFullYear(); | |
| 162 | + await tx.insert(paperSequences).values({ year, last: 0 }).onConflictDoNothing(); | |
| 163 | + const [seq] = await tx | |
| 164 | + .update(paperSequences) | |
| 165 | + .set({ last: sql`${paperSequences.last} + 1` }) | |
| 166 | + .where(eq(paperSequences.year, year)) | |
| 167 | + .returning({ last: paperSequences.last }); | |
| 168 | + manifest.paperNumber = `AS-WP-${year}-${String(seq!.last).padStart(4, "0")}`; | |
| 169 | + const manifestSha256 = createHash("sha256").update(JSON.stringify(manifest)).digest("hex"); | |
| 161 | 170 | const [row] = await tx |
| 162 | 171 | .insert(researchRuns) |
| 163 | 172 | .values({ |
modified
packages/schemas/src/run.ts
+4 −0
@@ -103,6 +103,10 @@ export const RunManifest = z.object({ | ||
| 103 | 103 | createdAt: z.string().datetime(), |
| 104 | 104 | createdBy: z.string().uuid(), |
| 105 | 105 | title: z.string().min(3).max(300), |
| 106 | + paperNumber: z | |
| 107 | + .string() | |
| 108 | + .regex(/^AS-WP-\d{4}-\d{4}$/) | |
| 109 | + .optional(), | |
| 106 | 110 | }); |
| 107 | 111 | export type RunManifest = z.infer<typeof RunManifest>; |
| 108 | 112 | |
modified
services/publisher/src/index.ts
+9 −1
@@ -156,7 +156,15 @@ export async function publishRun( | ||
| 156 | 156 | slug = p.slug; |
| 157 | 157 | versionNo = (last?.n ?? 0) + 1; |
| 158 | 158 | } else { |
| 159 | − publicId = await allocatePublicId(tx, year); | |
| 159 | + const reserved = (run.manifest as { paperNumber?: string }).paperNumber; | |
| 160 | + if (reserved) { | |
| 161 | + const [taken] = await tx | |
| 162 | + .select({ id: papers.id }) | |
| 163 | + .from(papers) | |
| 164 | + .where(eq(papers.publicId, reserved)) | |
| 165 | + .limit(1); | |
| 166 | + publicId = taken ? await allocatePublicId(tx, year) : reserved; | |
| 167 | + } else publicId = await allocatePublicId(tx, year); | |
| 160 | 168 | slug = slugify(meta.title, publicId); |
| 161 | 169 | versionNo = 1; |
| 162 | 170 | const [p] = await tx |
modified
services/research-runner/src/stages.ts
+2 −2
@@ -210,7 +210,7 @@ Write results/robustness.md: what was tested, what held, what weakened or overtu | ||
| 210 | 210 | Write the working paper in LaTeX under manuscript/ using the provided class: \\documentclass{autoscholar} (manuscript/autoscholar.cls is already there; read its header comments for the metadata commands \\aswpnumber, \\aswpversion, \\aswpdate, \\keywords, \\jelcodes (optional), abstract environment, theorem environments, \\asdisclosure is automatic). Use natbib with \\bibliographystyle{autoscholar} (provided) and \\bibliography{references} — manuscript/references.bib is the platform-generated file; cite ONLY keys present in it. |
| 211 | 211 | Structure: title, abstract (150–300 words), introduction/motivation, related work, theory/methodology, data (if any), results, robustness, discussion incl. competing explanations, limitations, conclusion, references, appendices (proofs, protocols, reproducibility, extra tables). Include figures from results/figures (\\includegraphics) and tables from results/tables (\\input). Every number must come from results/ and be registered with record_reported_value. Genuine intellectual depth, no filler, no exaggerated novelty, honest about weaknesses. |
| 212 | 212 | Also write: manuscript/abstract.md (plain-text abstract) and manuscript/metadata.json: {"title","abstract","keywords":[4-8],"discipline":"${m.discipline}","researchMode":"${m.researchMode}","secondaryModes":[...],"contributions":[2-5 strings],"methods":"...","mainResults":"...","limitations":"...","dataAvailability":"...","codeAvailability":"...","ethics":"...","synthetic": boolean (true if any synthetic data/simulation underlies results),"language":"en"}. |
| 213 | −Then call latex_build until it succeeds with zero errors and inspect the log for overfull boxes and undefined references. Set \\aswpnumber{pending} and \\aswpversion{1}.`, | |
| 213 | +Then call latex_build until it succeeds with zero errors and inspect the log for overfull boxes and undefined references. Set \\aswpnumber{${m.paperNumber ?? "pending"}} and \\aswpversion{1} (keep version 1 through revisions; the platform increments versions on corrections).`, | |
| 214 | 214 | check: async (ws) => { |
| 215 | 215 | const problems: string[] = []; |
| 216 | 216 | for (const f of [P.mainTex, P.manuscriptBib, P.abstract]) |
@@ -248,7 +248,7 @@ Write review/review-1.md (narrative referee report with major/minor issues and p | ||
| 248 | 248 | An independent reviewer produced review/review-1.md and review/review-1.json. Address every finding: fix the manuscript, analysis or proofs where the reviewer is right; where you disagree, explain why with evidence. Then write review/revisions.md (point-by-point response) and update review/review-1.json by setting each finding's "resolution" to "resolved" or "wontfix" with a "resolutionNote" (you may not delete findings or change severities; the platform re-checks blockers). |
| 249 | 249 | ${ctx.reproduction ? `The platform re-executed code/reproduce.sh in a clean environment and rebuilt the PDF; outcome: ${JSON.stringify(ctx.reproduction).slice(0, 1500)}. Reconcile any discrepancy (fix the script, the numbers, or document what could not be reproduced in the manuscript's reproducibility appendix).` : ""} |
| 250 | 250 | ${ctx.previewFiles?.length ? `Page previews of the current PDF are at: ${ctx.previewFiles.join(", ")} — Read them (they are images) and fix real layout defects (overfull lines, broken floats, unreadable figures, empty pages, bad tables).` : ""} |
| 251 | −Finally: ensure manuscript/metadata.json, abstract.md and the LaTeX title/abstract agree; make sure every reported number is registered; rebuild with latex_build until clean; verify the bibliography compiled (no [?] citations) by searching the PDF text. Also write manuscript/reproducibility.md: environment, versions, commands, seeds, data hashes, what reproduced and what did not.`, | |
| 251 | +Finally: ensure manuscript/metadata.json, abstract.md and the LaTeX title/abstract agree; keep \\aswpnumber{${m.paperNumber ?? "pending"}} and \\aswpversion{1}; make sure every reported number is registered; rebuild with latex_build until clean; verify the bibliography compiled (no [?] citations) by searching the PDF text. Also write manuscript/reproducibility.md: environment, versions, commands, seeds, data hashes, what reproduced and what did not.`, | |
| 252 | 252 | check: async (ws) => { |
| 253 | 253 | const problems: string[] = []; |
| 254 | 254 | if (!(await exists(ws, P.revisions))) problems.push("review/revisions.md missing"); |
| 255 | 255 | |