proof-rewrite-integrated
claude-opus-5-5Claude Codehigh effortrun 12 Oct 2026236,827,101 tokens
Instructions ▾
Instructions — what the model was asked
This folder is a frozen copy of the Proof repository, taken at the March 17, 2026 collaboration outage. It is a git repository whose first commit is that frozen state. Its npm dependencies are already installed in node_modules/ and work offline: npm test, npx tsc and npx tsx run from here with no network. Do not try to install or fetch anything.
Before investigating, read AGENTS.md, AGENT_PROMPT.md, benchmark_context/issue-557.md and benchmark_context/local-only-rules.md in full. The task in AGENT_PROMPT.md is quoted below; the “How to work” instructions here extend it from a plan to an integrated implementation and verification. For implementation scope, these instructions supersede any plan-only wording and any repository guidance that requires retaining the legacy architecture. Where a general instruction conflicts, the Benchmark Fixture Override in AGENTS.md wins. Paths in those files that point to another machine mean this folder.
This is a blind, local-only benchmark. Use only the files in this folder and its git history. Do not use web search, external services or any other copy of Proof.
The task, in the owner's words:
The code in this repo is vibecoded slop and it just keeps going down, and there's tons and tons of unrelated issues that are cropping up where it goes down or documents get duplicated, and I'm just tearing my hair out on it. I have a feeling that it's just vibecoded slop. If we started from the beginning, the codebase, especially around the live document collaboration, we would structure it way differently.
So if we wanted to do a clean first-principle structural rewrite where we were not thinking about, okay, what are the implementation services that we keep consistent? How do we do a clean migration? We just started from the beginning as a clean concept. What would we do? How would we structure it? What are the invariants that we would hold to be true throughout the codebase? Make a plan for that.
How to work:
- If you have to choose between a greenfield and a brownfield approach, choose GREENFIELD. Greenfield means a clean architecture for the working app, not a standalone prototype. You do not need a production migration or gradual rollout.
- Write your plan first and save it in
docs/plans/. - Then implement the plan in this folder. The default build and
npm run servemust use the new core through the existing editor and APIs. Preserve the app’s intended capabilities, fix the incident failures, and route normal reads and writes through the new core without falling back to the legacy implementation. Actually launch the integrated app and exercise it yourself locally, and write and run automated integration/end-to-end tests against the running client/server paths, including multiple clients, persistence across restart, and the incident regressions or their mapped equivalents wherever the fixture permits. Use a browser if one is available; otherwise exercise the client/server API and collaboration protocol paths with local clients. Writing tests without running them is not verification. Retire an existing test only by naming it and what now covers it, including the regression tests written after past incidents; tests for anything you did not replace stay and still pass.npm run build,npm run test:ciandnpm run test:pr:collabpass today and must pass at the end. Do not change.github/workflows; if a script runs a test you retired, update that script and say why inANSWER.md. - Before you stop, write
ANSWER.mdin the root of this folder. This file is required: it is your final report, and a chat reply alone is not read. It must contain:- the scope you delivered, and where the plan and the new code live
- every build, typecheck and test command you ran, with its result
- a capability inventory showing what was kept, changed or is missing, and the regression coverage
- what you directly observed in the running app versus what remains untested, including any fixture or runtime limitations
- the limitations and gaps that remain
About this sandbox: the internet is blocked, but you can start servers and connect to them on 127.0.0.1. $PORT holds a free port for this run, and Proof's server uses it. There is no writable /tmp; use $TMPDIR instead, for example APPSIGNAL_LOG_PATH=$TMPDIR npm run serve. This is one non-interactive session: it ends when you end your turn, and anything still running in the background is stopped then, so wait for background work to finish before you end your turn.
Proof: a clean document core for live collaboration
1. What I delivered
I took the greenfield route and integrated it into the running app.
Plan: docs/plans/2026-10-12-greenfield-document-core-plan.md. It covers:
- why the system keeps going down and duplicating content (section 1);
- the clean concept and 11 invariants (section 2);
- the module layout and data model (section 3);
- browser changes (section 4), API compatibility (section 5) and verification (section 6);
- the decisions I made while implementing it (section 6a) and the independent review findings and fixes (section 6b).
New code, which is the only authority for documents in the running app:
| Area | Files | Responsibility |
|---|---|---|
| Document core | server/core/ (17 files, ~3.6k lines) |
store.ts (SQLite: append-only Yjs update log + snapshot + derived read model), room.ts / rooms.ts (exactly one in-memory writer per document), sync-server.ts / sync-protocol.ts (Hocuspocus-provider-compatible WebSocket protocol, owned by us), content.ts (markdown ⇄ ProseMirror ⇄ Yjs via structural diffs), projector.ts, marks.ts, blocks.ts, access.ts, budget.ts (event-loop work budget), health.ts, core.ts (facade) |
| HTTP layer | server/http/ (10 files, ~2.4k lines) |
/api/documents/*, /api/agent/*, /share/markdown, /d/:slug (SPA, JSON, markdown, agent HTML), bridge, library/dashboard, Every auth, /ws routing |
| Composition | server/index.ts (rewired), server/http/app.ts, server/aux-store.ts |
npm run serve → server/bootstrap.ts → document core. Auth sessions and incident breadcrumbs live in aux-store, not the legacy schema. |
| Browser | src/editor/share-collab-bind-reset.ts, src/editor/index.ts (one method) |
The browser never re-seeds the shared Y.Doc. Hydration no longer depends on the removed markdown Y.Text. |
Removed:
- 32 legacy authority modules, about 36.8k lines:
collab.ts(12.5k lines),routes.ts,agent-routes.ts,canonical-document.ts,document-engine.ts,db.ts,collab-mutation-coordinator.ts,ws.ts,bridge.ts,library.ts,dashboard.ts,snapshot.ts, themutation-*modules,yjs-volume-maintenance.ts, and others. - 6 scripts that only operated on the legacy schema.
- 204 tests of the removed modules. Each one is listed with what now covers it in
docs/plans/2026-10-12-test-retirement-map.md. - Nothing in the default build or
npm run serveimports the legacy stack, and there is no fallback to it: the files are gone.scripts/typecheck-core.mjsplus a dangling-import scan confirm this.
Housekeeping.
- All changes are in the working tree and uncommitted.
git statusshows the deletions plus the newserver/core/,server/http/andsrc/tests/core-*files. preview.jsonstarts the app withnpm run build && npm run serveon port 4000.
The invariants (short form)
- One authority: a document's content is its Yjs update log. Markdown, marks JSON, revision and block refs are derived from it and never written back.
- One writer per document: browser sync, agent edits, REST PUT, comment ops and library sync all go through that document's single
Room, synchronously. - History only grows: a document's lineage is created once and never re-seeded. Whole-document writes are minimal structural diffs (
updateYFragment). - Durable before visible: an update is appended to SQLite before it is broadcast or acknowledged. There is no debounced
onStoreand no "stale persist". - The read model is derived and side-effect free: reads never mutate anything; a projection failure only lags that one document.
- Failure is scoped to the document: no global breakers, no cross-document fences, and a corrupt document degrades to read-only on its own.
- Bounded event-loop work: size limits plus a per-document and global CPU budget.
/healthis memory-only and reports event-loop lag. - Access fences per document: only access changes bump the epoch, and doing so closes only that document's sessions.
- Idempotent creation:
Idempotency-Keyon create, andGET /newignores prefetch and unfurl bots. - Clients write content only through the CRDT: HTTP content writes are preconditioned diffs, and a blind PUT is refused while editors are live.
- Boring shutdown: stop accepting, close sockets, flush derived state. There is nothing that could be lost.
How this maps to issue #557 and the duplication reports
-
The outage.
- Diagnosis (plan §1.4):
noteGlobalCollabAdmissionPathologytripped when two staleonStorereloads coincided while four or more documents anywhere in the DB were durably quarantined. Quarantine never expires and the count was a full-tableCOUNT(*). - Once tripped, it bumped the access epoch of every recently active slug and blocked live collab for every pre-existing document (the recorded scoped-slug set is dead code).
- It then re-tripped with a broader cohort. Meanwhile synchronous SQLite reads and Yjs replays ran on the same event loop that serves
/and/health. - In the new core the whole mechanism is gone, and the mapped regression test (
core-incident-557-regression) applies the same pressure pattern and asserts the properties the issue asks to preserve.
- Diagnosis (plan §1.4):
-
Duplication. The structural causes were:
- clear-and-reinsert writes into the CRDT from several places, including the browser (
resetCollabYDocForRebind); - re-seeding a document's lineage from the markdown row;
- replaying buffered client updates into a re-seeded lineage.
Invariant 3 removes all three.
core-store-roomincludes a negative control showing that the old shape duplicates and the new one does not. - clear-and-reinsert writes into the CRDT from several places, including the browser (
2. Commands I ran and their results
Environment: Node v26.10.0, offline, in a sandbox with no writable /tmp and no browser binary.
Baseline, before any change
| Command | Result |
|---|---|
npm run build |
✅ exit 0 |
npm run test:ci |
✅ exit 0 (9 tests) |
npm run test:pr:collab |
❌ exit 1 in this sandbox: AppSignal tried to log to /tmp. ✅ with APPSIGNAL_LOG_PATH=$TMPDIR |
npm test |
❌ exit 1: pre-existing failure in proof-mark-rehydration.test.ts. The && chain stops there. |
Each of the 140 test files referenced by test, test:server-routes-share, test:pr:collab, test:ci and test:collab:stability-followups, run individually with a 240 s timeout |
130 pass, 10 fail (pre-existing): proof-mark-rehydration, mutation-contract-stage-and-ws (hangs), collab-regression-guards-round2, live-viewer-passive-reconnect-wiring, share-new-doc-bootstrap-editability-regression (both need Chromium), share-live-viewer-rest-put-authority-regression, agent-edit-fragment-base-regression, agent-mutations-imply-presence, share-json-repair-readiness-contract-regression, collab-corrupt-persisted-yjs-quarantine |
npx tsc -p tsconfig.server.json --noEmit |
❌ 256 errors (pre-existing) |
npx tsc --noEmit -p tsconfig.json |
❌ 1127 errors (pre-existing) |
Final
All commands were run from this folder on the final code. Commands with no env prefix were run with no extra environment.
| Command | Result |
|---|---|
npm run build |
✅ exit 0 (Vite client build + build:mcp) |
npm run test:ci |
✅ exit 0 (9/9) |
npm run test:pr:collab |
✅ exit 0: 14 suites, 68 tests (the 3 unchanged Yjs/Hocuspocus contract suites + npm run test:core, 11 new suites). It now passes without the APPSIGNAL_LOG_PATH workaround, because AppSignal init fails open. On the final code it passed twice as npm run test:pr:collab and once more file-by-file. Four earlier runs during development, before the review fixes, were also green. About 25 s per run. |
npm run typecheck:core (scripts/typecheck-core.mjs, strict tsconfig.core.json over server/core, server/http, server/index.ts, src/tests/core-*) |
✅ 0 errors in new code. Reports, without gating, 31 pre-existing errors in imported legacy Milkdown schema/parser modules. |
npx tsx src/tests/server-routes-and-share.test.ts (98-case legacy route suite, retargeted at the core routers) |
✅ 94 passed, 0 failed. Baseline was 96 passed; 2 cases tested deleted legacy guards and were retired, see the retirement map. |
Per-file run of all 67 test files referenced by test, test:server-routes-share, test:collab:contracts, test:pr:collab, test:ci, test:core and test:collab:stability-followups |
64 pass, 3 fail. The 3 are the same pre-existing failures as baseline: collab-regression-guards-round2 (a client wiring assertion), plus share-new-doc-bootstrap-editability-regression and live-viewer-passive-reconnect-wiring, which both need a Chromium binary. |
npm test |
❌ exit 1: the && chain stops at the pre-existing collab-regression-guards-round2 failure. Baseline also exited 1, stopping at proof-mark-rehydration, which is now retired. |
npm run test:server-routes-share |
❌ exit 1: stops at live-viewer-passive-reconnect-wiring (needs Chromium; fails identically at baseline). Every earlier entry in the chain passes. |
npx tsc -p tsconfig.server.json --noEmit |
❌ 78 errors, down from 256 at baseline. None are in server/core, server/http, server/index.ts or server/aux-store.ts. |
npx tsc --noEmit -p tsconfig.json |
❌ 512 errors, down from 1127 at baseline. The only ones in new files are the same rootDir TS6059 notices every server-importing test gets under this config. |
INCIDENT_DOCS=40 INCIDENT_LOAD_MS=30000 npx tsx src/tests/core-incident-557-regression.test.ts (the test:collab:nightly:matrix configuration) |
✅ in 4 runs (the last 2 on the final code): 80 live clients and 620–708 agent edits per run. /health p50 1 ms, p99 169–331 ms, max 192–541 ms; / p99 169–198 ms. 0 probe failures, 0 healthy-document sessions fenced, every acknowledged edit present exactly once. |
Manual: npm run serve + scripts/core-live-session-smoke.ts phase 1 → SIGTERM → npm run serve → phase 2 |
✅, done twice (before and after the review fixes). Details in §4. |
Test suites added (all in test:core, so they run in test:pr:collab):
| Suite | Tests |
|---|---|
core-store-room |
8 |
core-durability-regressions |
6 |
core-work-budget |
2 |
core-sync-protocol (real @hocuspocus/provider) |
5 |
core-shutdown-durability |
3 |
core-security |
5 |
core-api-regressions (ported legacy regressions) |
11 |
core-bridge-library |
2 |
core-review-regressions |
7 |
core-e2e-server (spawned server/bootstrap.ts + real web-client modules) |
6 |
core-incident-557-regression |
1 |
Scripts changed in package.json, and why:
test,test:server-routes-share: dropped the 84 retired test files and addednpm run test:core. The retired files import modules that no longer exist; what replaces each one is in the retirement map.test:pr:collab:test:collab:contracts && test:core. It replacescollab-shutdown-authority-regressionandcollab-shutdown-live-connection-drain-regression, which tested the deletedserver/collab.tsshutdown. They are replaced bycore-shutdown-durabilityandcore-e2e-server(SIGKILL/SIGTERM).test:collab:stability-followups,test:collab:nightly:matrix,test:collab:same-paragraph-race,test:live-viewer:fragment*: repointed at the core suites, since their old targets were retired.- Removed
test:migration-verify,repair:proof-marksandrepair:yjs:audit; their scripts operated only on the deleted legacy schema. - Added
test:coreandtypecheck:core. .github/workflowsis unchanged.
Independent review
After implementation I had two independent reviews, one of the core and one of the HTTP layer. The reviewers reproduced each finding with throwaway scripts.
- Core review: 4 high and 4 medium findings.
- A stale request-time record could seed a room and reopen an access fence.
- After a projection error, preconditions could pass against stale content and revert live edits.
- Converting the CRDT could make a read delete schema-unknown elements.
- A corrupt document's last good read model and history could be destroyed.
- Medium: retry timer, projection starvation, budget double-charging and starvation, ambiguous accept fallback.
- HTTP review: 8 findings.
- Tokenless browser actions on agent routes failed.
ownerIdspoofing into libraries, with title markup injection.- Viewers could write unbounded presence.
- Create idempotency was shared across callers.
- Library policy leaked into the room.
- Some malformed inputs returned 500.
- Idempotent replay ran before authorization.
- The bridge silently downgraded invalid tokens.
All of these are fixed and pinned by core-durability-regressions (6 tests) and core-review-regressions (7 tests). The four low-severity items I didn't fix are listed in §5.11. Plan §6b has the full list.
3. Capability inventory
Legend: ✅ kept (same contract, new core) · 🔁 changed (deliberately different) · ⛔ removed / missing.
| Capability | Status | Notes / regression coverage |
|---|---|---|
Create doc: POST /share/markdown, /api/share/markdown, legacy POST /api/documents (warn/disabled modes, auth modes, rate limit, validation codes) |
✅ (+ Idempotency-Key) |
server-routes-and-share, core-store-room, core-e2e-server (idempotent create) |
GET /new, /get-started |
✅ / 🔁 | Prefetch requests and unfurl bots get 204 and create nothing (duplicate-doc source removed). Covered by core-e2e-server. |
Share page /d/:slug: SPA with injected config, agent HTML, JSON manifest, text/markdown, token cookie, OG tags, comment-UI mode |
✅ | server-routes-and-share (retargeted). I fetched the page and the built bundle manually. |
| Paused/revoked share for non-owners | 🔁 | Generic "unavailable" page, 200 for HTML (legacy served a stored HTML snapshot), no title or content leak. JSON stays 404/410. |
/api/documents/:slug (+ /recovery, /info), open-context, collab-session, collab-refresh |
✅ | Same shapes the ShareClient parses. Covered by core-e2e-server through the real ShareClient / CollabClient modules. |
| Live collaboration (Hocuspocus provider protocol, awareness/cursors, read-only roles, token rotation) | ✅ (reimplemented server) | core-sync-protocol (real provider), core-e2e-server, core-security |
| Multi-client convergence, offline buffer replay, hard reconnect | ✅ | core-e2e-server (3 tabs, SIGKILL/SIGTERM restart, offline replay exactly once) |
PUT /api/documents/:slug (markdown / marks / title), PUT …/title |
✅ / 🔁 | A blind markdown PUT is refused with 409 PRECONDITION_REQUIRED while live editors are connected. Marks PUT is a monotonic merge (no lost replies, no resurrection). Covered by core-api-regressions and server-routes-and-share. |
Comments, replies, resolve/unresolve, suggestions (insert/delete/replace), accept/reject via /ops, /api/agent/:slug/marks/*, bridge |
✅ | Anchors are applied server-side in the same transaction as the metadata. Covered by core-api-regressions, core-store-room, core-bridge-library. |
Agent state, snapshot, edit, edit/v2 (all 6 ops), rewrite, ops, events/pending, events/ack, idempotent replay, mt1: base tokens |
✅ | core-api-regressions, core-e2e-server, server-routes-and-share |
rewrite.apply with live collaborators |
🔁 | Allowed: it is a precondition-checked diff, so there's no LIVE_CLIENTS_PRESENT, no barrier, and live sessions are not fenced. Covered by core-api-regressions. |
Agent presence/cursors (/presence, implied by mutations with X-Agent-Id, /presence/disconnect) |
✅ / 🔁 | Reads no longer write presence (legacy GET /state + X-Agent-Id did). The UA-based provisional "AI collaborator" presence was dropped. /presence now needs comment rights (legacy allowed viewers), and its fields are length-capped. Covered by core-api-regressions and core-review-regressions. |
Agent API auth (/api/agent/*) |
🔁 | Same policy as the share routes: an explicit token gives its role (invalid → 401), the owner's Every session is owner, and no token gets the link's default role. Legacy returned 401, which broke accept, resolve and event polling for tokenless share-page users. This grants nothing a tokenless user couldn't already do through /api/documents/*. Covered by core-review-regressions and core-security. |
| Account ownership and titles | 🔁 | ownerId: every:<id> on create is honoured only for that authenticated principal; opaque labels are kept. Titles are stored single-line, up to 300 characters. Covered by core-review-regressions. |
| Access links, pause/resume/revoke/delete | ✅ | Epoch bump fences only that document. Its sessions are closed with 4205 (reset), so the provider reconnects, gets session-stale and the editor's recovery runs. Covered by core-sync-protocol, core-e2e-server, server-routes-and-share. |
Bridge /d/:slug/bridge/* |
✅ (server-executed) | Legacy forwarding of unknown operations to a connected browser (bridge.request) is not reimplemented: every bridge operation runs on the server. Covered by core-bridge-library. |
| Every OAuth, library/dashboard, library access link, claim, delete | ✅ / 🔁 | The library is an ordinary owned (paused) doc whose content the server re-derives on open. It is not continuously synced in the background. Covered by core-bridge-library and server-routes-and-share. |
/health |
🔁 (stronger) | Memory only: starting / ready / degraded / stopping / stopped, event-loop lag, room/session counts, recent incident slugs. Covered by core-shutdown-durability and core-incident-557-regression. |
Metrics (/metrics, agent-mutation series used by alerting), bug reports, discovery docs, homepage, static assets |
✅ | appsignal-prod-alerting and test:ci (unchanged) |
| Quarantine, repair, clone-from-canonical admin routes; projection repair worker; global admission guard; hot-slug breaker | ⛔ by design | These routes return 410 RETIRED. The core has no repair state (invariants 1, 5, 6). |
Runaway-growth guard (PATHOLOGICAL_GROWTH_BLOCKED), block-explosion integrity quarantine |
⛔ by design | Replaced by structural impossibility of duplication (invariant 3), preconditions (10), size limits and the work budget (7). |
Legacy maintenance (mutation outbox/backfill, legacy mark-tombstone table, http-info incident rows) and their scripts (repair:proof-marks, repair:yjs:audit, test:migration-verify) |
⛔ | Tables are gone. The core prunes idempotency keys after 24 h and keeps its own mark tombstones, recorded when a mark leaves the CRDT, so stale snapshots can't revive it. |
Stored HTML snapshots / CDN snapshot publishing (snapshotUrl) |
⛔ / 🔁 | snapshotUrl is null. /snapshots/:slug.html renders a share-preview page (title + excerpt) on demand from the read model for active docs; no stored full-HTML snapshot. |
Separate-port collab runtime (COLLAB_PORT), PROOF_COLLAB_V2 kill switch |
🔁 | Collab always runs on the same HTTP server at /ws (the legacy "embedded" mode). COLLAB_PUBLIC_BASE_URL is still honoured for the advertised URL. |
Regression coverage for the incident and duplication reports:
- #557 (outage): core-incident-557-regression. Default run: 12 documents × 2 live clients, agents, access churn, a corrupt document and a 260 KB document. Nightly: 40 × 2 for 30 s.
- Stale-onStore / quarantine / fencing family: mechanisms removed. Covered by core-store-room, core-sync-protocol, core-shutdown-durability.
- v2 duplication and replay-into-reseed duplication: core-store-room negative control, plus core-e2e-server (offline replay, fresh-doc joins, restarts).
- Passive viewers never write: core-e2e-server (viewer edit never persists, revision unchanged), core-sync-protocol.
- Side-effect-free reads: core-e2e-server, core-api-regressions (reads don't change revision or log head).
- Stale sessions can't write after access changes: core-sync-protocol, core-e2e-server.
- Health stays responsive during degradation: core-incident-557-regression.
- Guard trips scoped to the causal document: core-incident-557-regression asserts that the only incident slug is the corrupt document and that no healthy document's session is closed.
4. What I observed in the running app, and what remains untested
I launched the integrated app myself, twice: once mid-way and once on the final code after the review fixes. The second time it ran with no APPSIGNAL_LOG_PATH. Each time I ran npm run build, then PORT=<free port> DATABASE_PATH=$TMPDIR/…/proof.db npm run serve, and drove it with scripts/core-live-session-smoke.ts. That script uses the real web-client modules (ShareClient, CollabClient and the Hocuspocus provider) plus plain HTTP. What I saw:
/healthreportedreadyon the first probe./returned 200,/favicon.ico200, and/newreturned 302 to/d/<slug>?token=…with the owner cookie set.- The share page returned 200 with
window.__PROOF_CONFIG__.shareSluginjected, and its built bundle/assets/editor-*.jsreturned 200 (3.5 MB). - Two tabs opened with
canEdit:trueand converged on each other's edits. - An agent read
/state, added a comment and a replace suggestion. The comment arrived in tab A through the CRDT, and accepting through the realShareClient.acceptSuggestionchanged the text in tab B. - Agent presence
ai:plannerappeared in tab A'sagentPresencemap. A title change reached the viewer socket, which sawconnected, viewers.updated, document.updated, agent.presence, document.title.updated. Accept: text/markdownreturned the clean document./healthshowed 1 room and 2 sessions.- Restart. I stopped the server with SIGTERM; the log showed
[shutdown] complete in 2ms. I then started it again on the same database:- every line was present exactly once, the comment survived and the title and revision were intact;
- a new tab hydrated the full document, comment anchor included;
- an edit made after the restart persisted.
Extra probes against the final running server:
/d/<slug>withAccept: application/jsonreturned the manifest (15 links,mutationReady:true).- Tokenless
open-contextreturned an editor session onws://…/ws?slug=…withpm-yjs-v1. /agent-docs,/.well-known/agent.jsonand the OG PNG all returned 200.- The legacy
/api/agent/<slug>/quarantinereturned 410.
Exercised automatically against real processes:
core-e2e-serverspawnsserver/bootstrap.ts, the exactnpm run serveentry point. It runs three simulated browsers on the real client modules plus agents, and kills the server with SIGKILL and SIGTERM mid-session.core-incident-557-regressionruns the outage pressure pattern against the spawned server.
Sandbox note. An early smoke-test server, started before I switched to harness-tracked background tasks, could not be signalled from this sandbox ("operation not permitted"). It kept $PORT busy, so my later manual runs used other free ports. It stops when the session ends.
Not exercised:
- A real browser. There is no Chromium or other browser binary in this sandbox: Playwright's browser cache is absent and no system browser is installed. So the Milkdown editor UI (ProseMirror view,
ySyncPluginbinding, cursors plugin, comment UI) was not driven interactively. The browser-side collab/network code (ShareClient,CollabClient, the Hocuspocus provider) did run, in Node with a minimalwindowshim. Editor typing was simulated by applyingupdateYFragmentdiffs to the client's Y.Doc, which is what y-prosemirror's sync plugin does. share-new-doc-bootstrap-editability-regressionandlive-viewer-passive-reconnect-wiringboth need Chromium. They fail here exactly as they did at baseline, and should run against the new server where a browser exists. The two browser changes are small: the client never re-seeds the shared Y.Doc, and hydration ignores the emptymarkdownY.Text. Both are unit-tested.- Every OAuth against the real provider (offline). The OAuth flows ran against the test suite's mock, and the library against a locally stored, recently verified session.
- Staging and production scripts (
scripts/live-viewer-*,staging-*,prod-*) need remote deployments, which the benchmark forbids. - The native macOS app and its local bridge (
localhost:9847).
5. Limitations and gaps
- One process owns the rooms.
- Invariant 2 holds per process. The core does not coordinate several server instances writing the same document.
- If two instances did load one document, the
(doc_id, seq)primary key makes the second append fail. That fails closed (the room drops, the client re-sends), so content isn't corrupted, but the system would churn. - Scaling out needs slug-sticky routing or a per-document lease in front of the core. Production today ran as a single Railway service.
- One operation's cost still scales with document size.
- The work budget caps throughput, per document and globally, and keeps
/healthand public routes responsive. Measured/healthp99 was 169–331 ms with 80 live clients plus agents. - But one parse or serialize of a large document is still synchronous: about 0.5 s for 260 KB, and roughly 2 s near the 1 MB
PROOF_MAX_MARKDOWN_BYTESlimit. - The next step is a worker-thread content engine, so the event loop only ever does O(update) work.
- The work budget caps throughput, per document and globally, and keeps
- Durability level.
synchronous=NORMALin WAL mode survives a process crash (tested with SIGKILL) but not power loss.PROOF_SQLITE_SYNCHRONOUS=FULLtrades throughput for that. - No data migration. As the task allowed, documents in the legacy tables (
documents,document_y_updates, …) are not imported, so a database from the old server starts empty under the core. A one-shot importer would be about 100 lines: create a core document with the legacy slug, owner-secret hash and access-token hashes from the legacy markdown and marks. - Agent presence is written to the durable CRDT log. It is small and bounded (50 activity entries), but it is not ephemeral the way awareness is. Moving agent presence to awareness would need a client change in
src/editor/index.ts. - Behavior changes callers might notice (all listed in §3):
- blind PUTs are refused while editors are live;
rewrite.applyis allowed with live clients;- reads no longer create agent presence;
- paused shares show a generic page instead of a stored snapshot;
snapshotUrlisnull;- quarantine, repair and clone routes return 410;
- unknown bridge operations are no longer forwarded to a connected browser;
- the library is re-derived on open rather than continuously in the background;
- HTTP markdown writes are capped at 1 MB (
PROOF_MAX_MARKDOWN_BYTES). Legacy projection guards tripped at 1.5 M characters.
- Browser UI not exercised, because no browser binary is available (§4). The client collab and network modules ran for real in Node, but the editor view did not.
- CI path filter.
.github/workflows/ci.yml(which I was not allowed to change) triggerstest:pr:collabon the legacy filenames. Future PRs that only touchserver/core/orserver/http/won't trigger it. The filter should add^server/(core|http)/and^src/tests/core-. - Typecheck.
- The repo-wide configs
tsconfig.server.jsonandtsconfig.jsonfailed before this work (256 and 1127 errors) and still fail (78 and 512). None of the remaining errors are in new server code. - The new code is gated by
npm run typecheck:core(strict, 0 errors). It reports, but doesn't gate, the 31 pre-existing errors in the legacy Milkdown schema and parser modules it imports.
- The repo-wide configs
- Pre-existing failures left alone.
collab-regression-guards-round2is a client-source wiring assertion that fails identically at baseline. Because thenpm testchain is&&-joined,npm teststill stops there, as it stopped at the pre-existingproof-mark-rehydrationfailure before.share-new-doc-bootstrap-editability-regressionandlive-viewer-passive-reconnect-wiringneed Chromium.
- Smaller known gaps (from the review; documented rather than fixed):
- A stale browser snapshot carrying
resolved: truecan re-resolve a comment that was unresolved after the snapshot was taken. Unresolving itself goes through its own endpoint. - A
document_too_largedegradation does not clear itself after compaction. - Room loads,
createDocumentand/snapshotare measured but not admission-gated by the work budget. They are bounded by the size limits and the create rate limit. - An explicit server-side write to a document containing node types unknown to the server schema (e.g. from a newer browser build) removes those nodes. Reads never do; they convert a detached copy.
- A stale browser snapshot carrying
- Some docs still describe legacy mechanisms.
AGENTS.mdnow carries a superseding note, but the older guardrail bullets,CLAUDE.mdand the reviewer specs still describe them.