Skip to content

Add ephemeral paper and Lean uploads - #18

Merged
jjjhenriksen merged 1 commit into
mainfrom
codex/ephemeral-uploads
Jul 14, 2026
Merged

jjjhenriksen merged 1 commit into
mainfrom
codex/ephemeral-uploads

Conversation

@jjjhenriksen

Copy link
Copy Markdown
Owner

Summary

  • add a temporary /upload workspace for reader-provided PDF papers and optional Lean source
  • parse PDF and Lean/ZIP inputs in the browser, with explicit rights consent and no durable storage
  • stream explanations through a separate bounded /api/explain-upload context boundary
  • label every uploaded Lean source as unverified and paper-to-Lean correspondence as provisional
  • document privacy, security, resource limits, and non-goals

Trust and privacy

Original PDF, Lean, and ZIP bytes stay in the browser. The server receives only the selected passage and bounded nearby text after an explicit explanation request. Uploaded context always reports verification as not-run; it cannot inherit verification evidence from registered proof packages.

ZIP inputs are bounded before expansion and reject traversal paths, absolute/backslash paths, oversized entries, invalid UTF-8, duplicates, and collection limit violations.

Validation

  • npm run proof:validate
  • npm run eval:validate
  • npm test — 29 files, 107 tests passed
  • npm run typecheck
  • npm run lint
  • npm run build
  • npm run build:sites
  • npm run test:e2e — 31 passed, 7 intentional skips
  • npx playwright test tests/e2e/upload-workspace.spec.ts after mobile coverage expansion — 4 passed across desktop and mobile

Review notes

This PR intentionally does not run Lean, persist uploads, publish user material into the proof library, or claim that an uploaded Lean theorem formalizes the uploaded paper.

@jjjhenriksen
jjjhenriksen marked this pull request as ready for review July 14, 2026 20:41
@jjjhenriksen
jjjhenriksen merged commit 3e781d9 into main Jul 14, 2026
1 check passed
@jjjhenriksen
jjjhenriksen deleted the codex/ephemeral-uploads branch July 14, 2026 20:41

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1a7757657

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

source: "lean",
file: activeLean.path,
selectedText,
surroundingText: activeLean.text.slice(0, 12_000),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Window Lean context around the highlighted text

For uploaded Lean files longer than 12,000 characters, highlighting code later in the file stores only the file prefix as surroundingText; uploadedExplainRequestSchema then rejects the request because the selected text is not contained in that context, so valid selections past the prefix get a generic 400 instead of an explanation. Build the bounded context around the actual selection rather than always slicing from the start.

Useful? React with 👍 / 👎.

Comment thread lib/uploads/lean-files.ts
for (const file of sourceFiles) {
const lowerName = file.name.toLowerCase();
if (lowerName.endsWith(".lean")) {
result.push(decodeLean(file.name, new Uint8Array(await file.arrayBuffer())));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check direct Lean file size before reading

For direct .lean uploads, the browser reads the entire file into an ArrayBuffer before decodeLean enforces the 256 KiB limit. If a reader accidentally selects a very large Lean file, this can load far more data into memory than the documented resource cap permits, unlike the ZIP branch which checks file.size first. Reject oversized direct files before calling arrayBuffer().

Useful? React with 👍 / 👎.

Comment thread lib/uploads/schema.ts
Comment on lines +32 to +36
mappedLean: z.object({
file: leanPathSchema,
text: z.string().trim().min(2).max(8_000),
}).strict().optional(),
mappedPaper: z.object({ page: z.number().int().min(1).max(100), text: z.string().trim().min(2).max(8_000) }).strict().optional(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject mismatched mapped sources in upload requests

A crafted /api/explain-upload request can include both mappedLean and mappedPaper for either selection type because these fields are independently optional. buildUploadedExplanationContext forwards both into the model context, but only exposes the opposite-source mapping in publicContext.sources, so the model can ground or cite a hidden/conflicting mapped source that the reader never sees as evidence. Add a refinement that only accepts the mapped source type valid for the current selection, and not both.

Useful? React with 👍 / 👎.

source: "paper",
page: pageNumber,
selectedText: value.selectedText,
surroundingText: activePage.text.slice(0, 12_000),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Window paper context around the selected passage

For an uploaded PDF page with more than 12,000 extracted characters, selecting a passage after that prefix stores only the start of the page as surroundingText. The upload schema requires the selected text to occur in the supplied surrounding text, so these otherwise valid page selections are rejected by /api/explain-upload with a generic invalid-context error. Slice a bounded window around the selected passage instead of always taking the page prefix.

Useful? React with 👍 / 👎.

Comment on lines +122 to +123
const browserSelection = window.getSelection()?.toString() ?? "";
const selectedText = boundedSelection(browserSelection || activeLean.text);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope Lean selections to the Lean source

When the browser still has text selected outside the Lean <pre>—for example after highlighting a paper passage and then switching tabs—the Explain this Lean file button uses that stale global selection instead of the file contents. If that text is not in the Lean source the request is rejected, and if it happens to match somewhere the explanation is for the wrong passage. Only use window.getSelection() when its anchor/focus are inside the active Lean source; otherwise fall back to the file text.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant