feat(file-system): optimistic-concurrency guard on canvas publish#72365
Conversation
|
Hey @adboio! 👋 It looks like your git author email on this PR isn't your
You can fix it for this repo with: git config user.email "you@posthog.com"Or set it globally with |
…as action canvas_publish now calls the desktop-fs canvas action, which owns version composition server-side, passing the checkout's version as expected_current_version_id so a stale publish is rejected atomically (409 version_conflict) instead of guarded by a non-atomic tool-side check-then-PATCH. Deletes the tool-side compose logic and the raw meta PATCH; backends predating the guard field ignore it and publish unguarded, so the tool degrades gracefully. Server side: PostHog/posthog#72365. Generated-By: PostHog Code Task-Id: 2b3d176e-3023-41d2-92e8-81eda7c12a8c
The desktop canvas publish action appends a version onto whatever the live head is, so a publisher working from a stale read (a concurrent publish, or a user's undo moving currentVersionId back) silently becomes the new head over newer work. Add an optional expected_current_version_id to the publish payload: when provided and the canvas has moved past it, the publish is rejected with 409 version_conflict (carrying the live version id) inside the existing row lock. Omitting the field keeps the previous unguarded behavior for older callers. Also mirror the client's linear-discard semantics: publishing after an undo now truncates the redo tail past currentVersionId instead of leaving unreachable versions in the history. Generated-By: PostHog Code Task-Id: 2b3d176e-3023-41d2-92e8-81eda7c12a8c
🤖 CI report✅ Bundle size — no changeUncompressed size of every built Total: 64.16 MiB · no change No file changed by more than 1000 B. Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report ✅ Eager graph — within budgetHow much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy
🟢 Largest files eagerly shipped from
|
| Size | File |
|---|---|
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 24.6 KiB | ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js |
| 6.3 KiB | ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js |
| 4.5 KiB | ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js |
| 3.9 KiB | ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js |
| 1.4 KiB | ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js |
| 1.3 KiB | src/RootErrorBoundary.tsx |
| 912 B | ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js |
| 789 B | src/scenes/ChunkLoadErrorBoundary.tsx |
| 762 B | src/index.tsx |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 281.3 KiB | ../node_modules/.pnpm/posthog-js@1.404.1/node_modules/posthog-js/dist/rrweb.js |
| 267.7 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 235.5 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 223.4 KiB | ../node_modules/.pnpm/posthog-js@1.404.1/node_modules/posthog-js/dist/module.js |
| 164.0 KiB | src/queries/validators.js |
| 154.3 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 105.9 KiB | src/lib/api.ts |
| 93.3 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
| 93.2 KiB | ../packages/quill/packages/quill/dist/index.js |
Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479
✅ Dist folder size — 🔺 +3.9 KiB (+0.0%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 1344.14 MiB · 🔺 +3.9 KiB (+0.0%)
a28994d to
9bec143
Compare
This stack of pull requests is managed by Graphite. Learn more about stacking. |
🦔 ReviewHog reviewed this pull requestFound no issues worth raising, so no review was posted. |
🦔 Hogbox preview · ✅ ready▶ Open the preview
commit |
|
Reviews (1): Last reviewed commit: "chore: update OpenAPI generated types" | Re-trigger Greptile |
There was a problem hiding this comment.
Additive, backward-compatible API contract change (optional guard field, unguarded behavior preserved when omitted) with parameterized tests covering exactly the failure modes that matter (false-positive guard, fail-open on stale/null base, undo/redo truncation), and it's already been reviewed by Greptile (agent reviewer) plus a 👍 from the security bot with no concerns raised.
- 👍 on the PR from greptile-apps[bot], hex-security-app[bot].
Gate mechanics and policy version
| Gate | Result | |
|---|---|---|
| prerequisites | ✓ | all clear |
| deny-list | ✓ | no deny categories matched |
| size | ✓ | 109L, 2F substantive, 275L/9F incl. docs/generated/snapshots — within ceiling |
| tier | ✓ | T1-agent / T1d-complex (275L, 9F, cross-cutting, feat) |
| stamphog 2.0.0b3 | .stamphog/policy.yml @ d5d244c · reviewed head 9bec143 |
…as action canvas_publish now calls the desktop-fs canvas action, which owns version composition server-side, passing the checkout's version as expected_current_version_id so a stale publish is rejected atomically (409 version_conflict) instead of guarded by a non-atomic tool-side check-then-PATCH. Deletes the tool-side compose logic and the raw meta PATCH; backends predating the guard field ignore it and publish unguarded, so the tool degrades gracefully. Server side: PostHog/posthog#72365. Generated-By: PostHog Code Task-Id: 2b3d176e-3023-41d2-92e8-81eda7c12a8c
…as action canvas_publish now calls the desktop-fs canvas action, which owns version composition server-side, passing the checkout's version as expected_current_version_id so a stale publish is rejected atomically (409 version_conflict) instead of guarded by a non-atomic tool-side check-then-PATCH. Deletes the tool-side compose logic and the raw meta PATCH; backends predating the guard field ignore it and publish unguarded, so the tool degrades gracefully. Server side: PostHog/posthog#72365. Generated-By: PostHog Code Task-Id: 2b3d176e-3023-41d2-92e8-81eda7c12a8c
…as action canvas_publish now calls the desktop-fs canvas action, which owns version composition server-side, passing the checkout's version as expected_current_version_id so a stale publish is rejected atomically (409 version_conflict) instead of guarded by a non-atomic tool-side check-then-PATCH. Deletes the tool-side compose logic and the raw meta PATCH; backends predating the guard field ignore it and publish unguarded, so the tool degrades gracefully. Server side: PostHog/posthog#72365. Generated-By: PostHog Code Task-Id: 2b3d176e-3023-41d2-92e8-81eda7c12a8c
…as action canvas_publish now calls the desktop-fs canvas action, which owns version composition server-side, passing the checkout's version as expected_current_version_id so a stale publish is rejected atomically (409 version_conflict) instead of guarded by a non-atomic tool-side check-then-PATCH. Deletes the tool-side compose logic and the raw meta PATCH; backends predating the guard field ignore it and publish unguarded, so the tool degrades gracefully. Server side: PostHog/posthog#72365. Generated-By: PostHog Code Task-Id: 2b3d176e-3023-41d2-92e8-81eda7c12a8c

Problem
posthog desktop canvas publish action (behind the
desktop-file-system-canvas-partial-updatemcp tool) has no concurrency guardthis is fine right now because every canvas write is a full file replace, but i am changing that (PostHog/code#3594), so version control is more important
Changes
expected_current_version_idas a concurrency guard on the PATCH; returns 409 on conflictHow did you test this code?
tested e2e manually, paired with PostHog/code#3594
Automatic notifications
Docs update
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Built with Claude (PostHog Code cloud task) while reworking desktop canvas generation to edit canvases as workspace scratch files (PostHog/code#3594). Skills invoked:
/improving-drf-endpoints,/writing-tests. Considered a tool-side check-then-PATCH guard in the agent instead; rejected because only a server-side check inside the row lock is atomic, and this endpoint already owns version composition — one canonical writer beats three divergent ones.Created with PostHog Code