feat(workflow): freeze a pinned version as the public copy - #7853
Draft
yangzhang75 wants to merge 2 commits into
Draft
feat(workflow): freeze a pinned version as the public copy#7853yangzhang75 wants to merge 2 commits into
yangzhang75 wants to merge 2 commits into
Conversation
A public workflow follows the author's latest content today: every save reaches the Hub immediately. Pinning a version as the public copy needs somewhere to keep that copy, which is what these columns are. `is_public` stays the on/off switch. `published_content` is the pin: NULL means the workflow follows the author's latest, which is what every workflow does today, so the migration changes nothing anyone can see. `published_name` and `published_description` travel with it because a pin has to hold everything on show, and `workflow_version` stores no metadata at all -- only content deltas. `published_version_id` names the version row holding that copy, so the revision panel can mark it and the author can restore it. The copy is materialized rather than replayed from `workflow_version` because those rows are reverse JSON-Patch deltas: serving a pinned workflow would mean folding every newer patch back from the author's current content on each public read, and a computed value is something the fulltext index cannot cover. A CHECK constraint makes "private but pinned" unrepresentable, and a PGroonga index mirrors the latest-content one so public search can match the frozen copy. Adding columns changes the arity of the generated positional constructor, so the three copy-producing paths (clone, duplicate, restore-a-version) now build their POJO with setters -- which is also what stops a later column from silently shifting a null into the wrong field. Part of apache#7828.
A public workflow follows the author's latest content, as publishing has
always done. This adds the other state: the author pins the version they
have now, and the public copy stops moving until they pin again.
`is_public` stays the on/off switch; `published_content` is the pin, NULL
while following. `WorkflowPublishService` owns the two states, and three
endpoints expose them: POST and DELETE `/workflow/pin/{wid}` to pin and
unpin, GET `/workflow/publish-status/{wid}` for what the author is shown.
Publishing and unpublishing move through the same service, so unpublishing
drops the pin rather than leaving a private workflow carrying one.
Two paths are narrowed so a pin can hold. A save wrote the whole row back,
so a publish landing while a save was in flight was silently rolled back,
and a request body could set the publish columns itself; saves now write
only name, description and content. Creating a workflow clears the publish
columns for the same reason.
Nothing reads the pinned copy yet: every workflow is in the following
state it is in today, and nothing on screen changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Automated Reviewer SuggestionsBased on the
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7853 +/- ##
============================================
+ Coverage 91.87% 91.89% +0.02%
- Complexity 4510 4517 +7
============================================
Files 1173 1174 +1
Lines 47350 47407 +57
Branches 5306 5319 +13
============================================
+ Hits 43502 43565 +63
+ Misses 2204 2191 -13
- Partials 1644 1651 +7
*This pull request uses carry forward flags. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Contributor
|
| config | throughput | MB/s | latency | max Δ latest / 7d | |
|---|---|---|---|---|---|
| 🔴 | bs=10 sw=10 sl=64 | 504 | 0.308 | 19,291/26,321/26,321 us | 🔴 +114.6% / 🔴 +83.0% |
| 🔴 | bs=100 sw=10 sl=64 | 1,232 | 0.752 | 82,153/90,442/90,442 us | 🔴 +20.4% / 🟢 -16.7% |
| 🔴 | bs=1000 sw=10 sl=64 | 1,433 | 0.874 | 694,496/774,324/774,324 us | 🔴 +6.4% / 🟢 +28.1% |
Baseline details
Latest main b7c33b0 from 2026-08-22T12:41:18.215Z
| config | metric | PR | latest main | 7d avg | Δ latest | Δ 7d |
|---|---|---|---|---|---|---|
| bs=10 sw=10 sl=64 | throughput | 504 tuples/sec | 1,070 tuples/sec | 833.79 tuples/sec | -52.9% | -39.6% |
| bs=10 sw=10 sl=64 | MB/s | 0.308 MB/s | 0.653 MB/s | 0.509 MB/s | -52.8% | -39.5% |
| bs=10 sw=10 sl=64 | p50 | 19,291 us | 8,990 us | 11,864 us | +114.6% | +62.6% |
| bs=10 sw=10 sl=64 | p95 | 26,321 us | 12,633 us | 14,381 us | +108.4% | +83.0% |
| bs=10 sw=10 sl=64 | p99 | 26,321 us | 13,861 us | 18,035 us | +89.9% | +45.9% |
| bs=100 sw=10 sl=64 | throughput | 1,232 tuples/sec | 1,420 tuples/sec | 1,083 tuples/sec | -13.2% | +13.7% |
| bs=100 sw=10 sl=64 | MB/s | 0.752 MB/s | 0.867 MB/s | 0.661 MB/s | -13.2% | +13.7% |
| bs=100 sw=10 sl=64 | p50 | 82,153 us | 69,686 us | 93,077 us | +17.9% | -11.7% |
| bs=100 sw=10 sl=64 | p95 | 90,442 us | 75,137 us | 99,553 us | +20.4% | -9.2% |
| bs=100 sw=10 sl=64 | p99 | 90,442 us | 89,601 us | 108,604 us | +0.9% | -16.7% |
| bs=1000 sw=10 sl=64 | throughput | 1,433 tuples/sec | 1,455 tuples/sec | 1,119 tuples/sec | -1.5% | +28.1% |
| bs=1000 sw=10 sl=64 | MB/s | 0.874 MB/s | 0.888 MB/s | 0.683 MB/s | -1.6% | +28.0% |
| bs=1000 sw=10 sl=64 | p50 | 694,496 us | 685,976 us | 909,247 us | +1.2% | -23.6% |
| bs=1000 sw=10 sl=64 | p95 | 774,324 us | 727,444 us | 952,561 us | +6.4% | -18.7% |
| bs=1000 sw=10 sl=64 | p99 | 774,324 us | 767,738 us | 985,186 us | +0.9% | -21.4% |
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,396.97,200,128000,504,0.308,19291.45,26320.69,26320.69
1,100,10,64,20,1623.23,2000,1280000,1232,0.752,82152.55,90442.03,90442.03
2,1000,10,64,20,13959.17,20000,12800000,1433,0.874,694495.56,774324.26,774324.26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #7828. Stacked on #7851 — until that merges this PR shows its commit too; the review here is the second commit,
feat(workflow): freeze a pinned version as the public copy.A public workflow follows the author's latest content, as publishing has always done. This adds the other state: the author pins the version they have now, and the public copy stops moving until they pin again.
What lands
is_publicstays the on/off switch.published_contentis the pin — NULL means following, non-NULL is the frozen copy.WorkflowPublishServiceowns the two states and the moves between them (publish,pinLatest,unpin,unpublish,statusOf), and three endpoints expose them:POST /workflow/pin/{wid}DELETE /workflow/pin/{wid}GET /workflow/publish-status/{wid}Publishing and unpublishing move through the same service, so unpublishing drops the pin rather than leaving a private workflow carrying one. Re-publishing starts in the following state: coming back should not silently put old public content on show again.
Two paths narrowed so a pin can hold
Saving a workflow read the whole row and wrote it all back. Two consequences, both fixed here by writing only name, description and content:
Creating a workflow clears those columns for the same reason.
Nothing on screen changes
No read path consults the pinned copy yet, and there is no UI. Every workflow is in the following state it is in today. The endpoints answer, and nothing calls them.
Tests
19 backend cases in
WorkflowPublishSpec: the state machine (follow → pin → re-pin → unpin → unpublish → re-publish), the guards (no write access, not published, workflow missing), that a create cannot inject publish columns, and that a save, a collaborator's save and a rename all leave the publish state alone.hasUnpublishedChangescompares the two copies as parsed JSON rather than as strings — the same graph can come back with its keys in another order, and reporting that as an edit is an alarm the author cannot clear. One case covers it.