Skip to content

Fix/known gaps - #3

Merged
muthanii merged 5 commits into
mainfrom
fix/known-gaps
Aug 2, 2026
Merged

muthanii merged 5 commits into
mainfrom
fix/known-gaps

Conversation

@muthanii

@muthanii muthanii commented Aug 2, 2026

Copy link
Copy Markdown
Owner

What changed and why

How it was verified

Checklist

  • pnpm typecheck && pnpm lint && pnpm test all pass
  • pnpm test:e2e passes, or this change cannot affect it
  • New exported function in packages/shared or lib/consensus has a unit test
  • Bug fix has a regression test that fails without the fix
  • No agent credential is logged, echoed, or returned anywhere
  • No change bypasses the consensus pipeline

Needs a maintainer's sign-off before merge

  • Adds a dependency
  • Changes the Y.Doc schema (live docs exist and cannot be recreated)
  • Changes the agent protocol (packages/agent-protocol)
  • Adds a new top-level route
  • Changes anything in the consensus engine or doc-guard

muthanii and others added 5 commits August 2, 2026 09:11
Every document change appended a row to yjs_updates and nothing ever removed
one, so replay cost and storage grew for the life of a board. Snapshots were
already being written; they just were not being used to retire the history
they contain.

Storing a snapshot now prunes in the same transaction. The ordering is what
makes it safe: read the update log's high-water mark BEFORE encoding, then
delete only at or below that mark. Hocuspocus applies an update to the
document before onChange persists it, so any row at or below the mark is
provably inside the snapshot encoded afterwards, while rows appended during
the encode land above it and survive. Replaying a redundant update is a no-op;
losing a live one is not, so the bias is deliberate.

Snapshots are capped too, keeping the newest few as a manual fallback. They
are the larger leak of the two, each being the whole document rather than one
delta.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PostgresPersistence had no tests. These pin the ordering rule compaction
depends on, including the case that matters most: an update appended while the
snapshot is being encoded must survive. Writing them caught a real defect —
snapshot retention was skipped whenever the update log happened to be empty,
so an idle board still accumulated snapshots without bound.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The build step's comment claimed it enforced the budget. Nothing did — it ran
`next build` and checked only that the build succeeded, so the board route
could have crossed the limit silently.

Next reports First Load JS gzipped (verified: a chunk reported as 54.2 kB
measures 53.0 KB gzipped and 169 KB raw), so its own figures compare directly
against the budget. The gate parses them rather than recomputing from .next/,
which keeps the number CI enforces identical to the number the table shows.

`shell: bash` on the build step is load-bearing: it brings `-eo pipefail`, so
piping through tee cannot mask a failed build.

Current state: 15 routes, worst is /b/[boardId] at 245.0 kB — 5 kB of headroom.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two entries were no longer true and one is now fixed: rate limiting shipped in
667b1ac, the e2e suite runs (18/18, repeatedly), and the Yjs update log is
compacted as of this branch. A gaps list that understates the product is as
misleading as one that overstates it.

Replaced with the gaps that are actually open, including several surfaced by
recent QA and DX passes but never written down: ws as a single point of
failure, the shared rate-limit bucket when no proxy header is present, the
absent Y.Doc migration path, and agent-protocol being unpublished with no
version on the wire.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Caught by reading the live ws log against the row counts in Postgres:
"prunedUpdates: 0, prunedSnapshots: 0" on every write, while the tables showed
the log emptied and snapshots capped. The deletes were correct; the telemetry
was lying.

postgres-js returns an Array SUBCLASS whose real figure is on `.count` and
whose `.length` is 0 without a RETURNING clause, so the Array.isArray branch
matched first and reported zero every time. Checking count/rowCount before the
array fallback fixes it — now the same run logs prunedUpdates: 1 alongside a
table that agrees.

The unit tests missed this because the in-memory fake returns plain values and
never modelled the driver's result shape, so the regression test pins the
shape itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@muthanii
muthanii merged commit 4726b63 into main Aug 2, 2026
1 check passed
@muthanii
muthanii deleted the fix/known-gaps branch August 2, 2026 06:25
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