Repository navigation
feat(overleaf): coordinator step 2 — inventory, review and review registry - #556
Conversation
Walks the manuscript folder within Overleaf's and Scient's limits, classifies every entry (managed, selected addition, local-only, unchecked build output or hidden, excluded) and reads only what the review needs. Limits are enforced before the bytes they bound are read; a file that changes while it is read fails the capture. Unmounted; step 2 of the coordinator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nnection The review of a manual sync, built from a captured folder and a fetched Overleaf head: the merge, conflict groups with their allowed choices, the candidate tree, incoming and outgoing changes, warnings and the exact plan the applier would run. It writes nothing to the folder and sends nothing. - Overleaf's content is held to the limits before any of it is read. - Local files Overleaf now has content at are captured and reviewed, never treated as absent; content at an excluded entry refuses the review. - The digest binds every input a confirmation must still match. Different choices or inputs give a new review. - The registry keeps one current review per connection for fifteen minutes, in memory only, consumed once by ID, digest and acknowledgements. - A staleness check names what changed: revision, base, epochs, authority, folder, capture or Overleaf head. Unmounted; step 2 of the coordinator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
- A never-synchronized local file that meets Overleaf content is a forced conflict decision; capturing it no longer enrols it in a clean merge. - Overleaf content is held to the folder's own rules (droppings, reserved names, staging, invalid paths, LFS pointers) before it can reach a plan. - Case and Unicode aliases are refused: a managed file on disk under another spelling is not a deletion, and differing spellings across sides stop the review. - Captures re-check every folder above a file before and after reading it, and that the path still names the file read. - Directories are iterated within the walk limit instead of read whole; nested repositories are recognised without listing them. - Candidate sizes are checked before any blob is read; merge-in-editor text is read only for an eligible group within the file limit. - Local-only decisions the connection cannot remember are refused. - The digest is canonical JSON and binds collisions and recorded markers. - Staleness compares a fresh rebuild of the review and names what changed, including inventory changes that leave the captured tree alone. - The registry drops expired reviews on every call and bounds how many reviews and target bytes it holds. - Typecheck fixes (widened limit types, explicit class field, typed test helpers, approved ID helper). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex review, round 1 (gpt-6.1-sol, high reasoning, read-only)Verdict on
Each P1 fix has a test that fails with the fix removed. 314 Overleaf tests pass. Round 2 is reviewing 🤖 Generated with Claude Code |
… identity A folder swapped for a link to an empty folder just before it was listed made its managed files look deleted, and the review proposed deleting them from Overleaf. Each folder's chain is now verified before and after it is listed, and every walked folder once more after the walk, before absence is trusted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex review, round 2 (gpt-6.1-sol, high reasoning, read-only)Verdict on
315 Overleaf tests pass, and typecheck, lint and knip are clean. Round 3 is reviewing 🤖 Generated with Claude Code |
…s whole chain Round 3 measured the folder checks growing with depth: 49 chains of 100 folders took 11.6 s instead of about 1 s. Each check now looks at one or two folders, and a sweep after the walk and another after the reads cover every walked folder once. Same tree: 1.2 s. The swap regressions still refuse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex review, round 3 (gpt-6.1-sol, high reasoning, read-only)Verdict on
A nested repository still gives
Accepted limit, and what later steps must do about it. A folder swapped and then restored between two checks can still make a file look missing for one capture. That is the non-atomic sampling the contract accepts (§2.8). Codex confirmed that a fresh capture and rebuilt review removes the false deletion, and that staleness reports
315 Overleaf tests pass; typecheck exits 0; lint and knip are clean. 🤖 Generated with Claude Code |
…e-20261013' into claude/overleaf-review-20261013
…an apply Codex round 4 on 82568ed found two plans a review would offer that the current file layer or applier refuses partway: - Git's folders: names now compare as the host's file layer compares them (upcased, so `.gıt` is `.GIT`; Windows' trailing dots, spaces and stream suffix; `GIT~1`). The inventory skips a repository folder under any name (HEAD plus objects and refs, or HEAD plus commondir) and refuses a manuscript folder inside Git's folders (`git-folder`). The review refuses Overleaf content that would make a folder a repository (`remote-forbidden`, kind `repository`). A test runs the production WorkspaceFileSystem against these rules. - Case-only renames: a path absent before and after, but spelled like a present file, stays in the review and the base and leaves the applier's plan with its renames. On a case-insensitive disk it resolves to the present file, and the applier cannot rename a file to its own name. Each fix has a test that fails with it removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex review, round 4 (gpt-6.1-sol, high reasoning, read-only)Brought up to date with main ( Verdict on
What held:
Each fix has a test that fails with it removed. 361 Overleaf, workspace and file-layer tests pass; typecheck, lint and knip are clean. Round 5 is reviewing 🤖 Generated with Claude Code |
Codex round 5 on ecbe66f showed that leaving such a rename's old spelling out of the applier's plan loses the old base identity when the apply is interrupted (contract section 2.5), and that choosing "mine" still left an alias-only rename the applier rejects, for case and NFC/NFD alike. The contract keeps alias-only renames a typed preflight refusal and says the coordinator does not work around them (section 5). The review now refuses with `alias-rename` whatever is chosen, and the plan filtering is gone. The refusal's test fails with it removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex review, round 5 (gpt-6.1-sol, high reasoning, read-only)Verdict on
Contract §5 already says alias-only renames "remain typed preflight errors; the coordinator does not invent a workaround". The round-4 filtering was such a workaround. To get past the refusal, undo the rename on Overleaf, or rename through another name with a sync in between. PR C must explain this. The refusal's test (case and NFC/NFD, no choice, "mine" and "theirs") fails with it removed. 361 tests pass; typecheck, lint and knip are clean. Round 6 is reviewing 🤖 Generated with Claude Code |
…tected renames Codex round 6 on d24a818: checking each rename's endpoints missed aliases across renames. With a.tex and z.tex synced and both deleted locally, Overleaf's A.tex (z's content) and b.tex (a's content) pair as z→A and a→b; "theirs" was confirmable, and the applier wrote A.tex, then found it under a.tex and stopped with attention. Every path the applier touches is in the base, Overleaf's tree or the capture, so the review now refuses `alias-rename` when two distinct paths among them share a collision key, before staging, whatever is chosen and whether or not Git saw a rename. The test adds the cross-rename case and a respelling with new content; it fails with the refusal removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex round 7 on fe80a5a: conflict groups add paths the applier checks that are outside base, Overleaf's tree and the capture. A group kept from an interrupted apply (A.tex) met Overleaf's a.tex; a name Git makes for a file meeting a folder (p~Scient) met an unchecked P~scient on disk. Either made a confirmable review whose apply stopped with attention. After conflict groups are built, and before any choice or blob read, every path the applier can check (base, Overleaf, capture, merge, rename ends, group paths) is compared with each other and with everything on disk; two spellings of one whole path refuse `path-alias`. Its test fails with the check removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex review, rounds 6 and 7 (gpt-6.1-sol, high reasoning, read-only)Both rounds found more ways for two spellings of one file (letter case or Unicode form) to reach the applier. On a Mac disk those are one file, so the apply stops partway with
What round 7 confirmed held:
Each check's test fails with it removed. 362 tests pass; typecheck, lint and knip are clean. Round 8 is reviewing 🤖 Generated with Claude Code |
…ead entry Codex round 8 on d94606d: comparing spellings missed two ways the applier reads a file nobody reviewed. Git's file/folder name `p~Scient` exactly matched an unchecked `p~Scient`, and a path kept from an interrupted apply (`p/x.tex`, or `P/x.tex`) lay inside a nested repository the walk skipped. Both gave confirmable reviews whose apply stopped with attention. The late check is now about occupancy: a path the applier can check that this review did not read is refused `unreviewed-path` (path, onDisk) when it matches an unchecked, local-only or skipped entry by collision key (same spelling or not), is another spelling of a walked folder, or lies below a skipped entry. Two spellings among the applier's own paths still refuse `path-alias`. Each part's test fails with it removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ublish Since #353 the applier writes only through native file publication and refuses elsewhere, so on Linux CI both review-to-applier tests failed with WorkspaceFileMutationUnavailableError. They now skip unless nativePublicationHost(), as the applier's own suites do. The file-layer agreement test treats an unavailable publication as "not refused": the Git folder refusal comes before any write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex review, rounds 8 and 9 (gpt-6.1-sol, high reasoning, read-only)Round 8 on
Two spellings among the applier's own paths still refuse Round 9 on
After round 9, CI only ( 🤖 Generated with Claude Code |
|
Ready to merge: Codex round 9 merge-ready on Two LaTeX editor browser tests from #353 failed on this branch's CI runs and passed on re-run:
This PR changes no web code, main is green at the merged commit, and both files pass locally (45/45). They are timing flakes on the CI runners, worth a separate fix on main. 🤖 Generated with Claude Code |
Overleaf manual sync, coordinator step 2: the review. Step 1 (#536) is merged; this PR targets
main. Plan, contract and decisions: #535,docs/internals/overleaf/(contract §5, §8, §11; plan §4.1–4.4, §4.9).Nothing here is wired into the app, and nothing writes to a manuscript folder or contacts Overleaf.
What it adds
OverleafInventory.tsOverleafReview.tsWorkspaceApplyPlanthe applier would run. Overleaf's tree is checked against the limits before any of its content is read (A24). Local files that Overleaf now has content at, under or above are captured and reviewed rather than treated as absent (A30). The digest covers every input a confirmation must still match.OverleafReviewRegistry.tsreplaced. The staleness check names what changed: connection revision, base, checkout or credential epoch, authority, folder identity, capture, or Overleaf head (a new commit with the same tree counts).OverleafRepository.treeSizesWhat a review refuses before it can be confirmed
A review is never offered for a plan the file layer or the applier would refuse partway:
remote-forbidden): droppings, LFS pointers, Git's or Scient's folder names as the file layer compares them (letter case,.gıt,GIT~1), or files that would turn a folder into a Git repository.path-alias), or since the last sync (alias-rename), whether or not Git saw a rename and whatever is chosen. Contract §5 keeps these typed refusals. To get past one: undo it on Overleaf, or rename through another name with a sync in between. PR C must explain this.unreviewed-path): an unchecked, local-only or skipped entry, the inside of a skipped folder, or another spelling of a folder. This covers names kept from an interrupted apply and names Git makes up for a file meeting a folder.Warnings and acknowledgements
unresolvedMarkers. Step 4 must refuse to publish while any remain.Tests (real Git, real folders)
OverleafInventory.test.ts(16): classification, exclusions (Git's folders under every name, bare repositories, linked worktrees), a file replaced by a folder, links and nested repositories, LFS pointers, collision capture, the exact 50,000,000-byte boundary, walk/file/total limits, a file changing mid-read, case and Unicode aliases, folders swapped for links mid-capture, a manuscript folder inside Git's folders.OverleafInventory.host.test.ts(2): runs the productionWorkspaceFileSystem: every Git path it refuses, the inventory excludes; folders it treats as repositories, the inventory skips.OverleafReview.test.ts(26): up to date, first sync, clean two-way merge, each conflict choice with a new digest, determinism, collisions as forced decisions, every refusal above with the scenario Codex used to find it, the canonical digest, size limits before reading, the local-only cap, warnings, interrupted files and unresolved markers.OverleafReview.apply.test.ts(2): runs the real applier on a real review's plan and checks the folder ends up exactly as reviewed. Skipped where the applier cannot publish natively (Linux CI), as the applier's own suites are.OverleafReviewRegistry.test.ts(21): consume once, replaced, expiry, sweeping, count and byte bounds, digest, acknowledgements, unresolved conflicts, invalidation, each staleness reason.vp test run src/scient/overleaf: 15 files, 248 tests.pnpm run typecheck(tsc) exits 0; lint, formatting andknip:checkare clean.Codex review (gpt-6.1-sol, high reasoning): merge-ready
Nine rounds, all recorded in the PR comments. Rounds 1–3 fixed 4 P1, 8 P2 and 1 P3. Rounds 4–8 ran after merging main, where #353 had rewired the applier through the shared file layer. They found one P2 about Git folder names and, in turn, every way two spellings or an unread entry could reach the applier. Each was fixed and its fix re-checked. Round 9 on
b40513cb88: merge-ready, no findings. It walked every path the applier reads, writes or guards, and checked that the ordinary flows are not refused.7405ad84f5after it only changes which machines run two tests.Not done in step 2
overleafReviewStalenesscompares the two); a swap-and-restore between checks can make one capture see a false deletion (contract §2.8).sweepon a timer.WorkspaceApplier.test.ts(620 → 194 lines). Codex re-probed two of their guarantees (an editor save just before apply survives; a root replaced by a link is refused) and both hold; restoring the rest should come before step 3.🤖 Generated with Claude Code