Skip to content

feat(workflow): serve the pinned copy to public viewers - #8575

Draft
yangzhang75 wants to merge 2 commits into
apache:mainfrom
yangzhang75:pin/3-read-paths
Draft

yangzhang75 wants to merge 2 commits into
apache:mainfrom
yangzhang75:pin/3-read-paths

Conversation

@yangzhang75

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

With a version pinned, a workflow has two copies. This routes every read that serves a viewer
without granted access through the frozen one.

  • The seamWorkflowAccessResource.hasGrantedAccess(wid, uid): granted access, as opposed
    to being able to read a workflow only because it is public. An owner or a shared user keeps
    tracking the author's latest, because sharing is not publishing. Everyone else is here only
    because the workflow is public, and gets the public copy.
  • One copy, not four fieldsWorkflowPublishService.PublicCopy carries the name, the
    description, the content and the default view together, and publicCopyOf hands them out as a
    group, so no surface can serve the published graph under a title the author never published, or
    open a form view over a copy whose content carries no form.
  • Routed through it — opening a workflow, the hub's read of a public workflow, Clone,
    Duplicate, /workflow_name, /workflow_description, and the size a listing shows.
  • The revision history is a door into the same room. Replaying a version folds deltas back
    from the author's current content, so listing or checking out versions would hand a public
    viewer the drafts the pin is holding back. canReadHistory keeps the history open to anyone
    with granted access, and to everyone while nothing is pinned — the public copy is then the
    author's latest and its history is the history of what everyone can already see.

A workflow that follows the author's latest — every workflow today — is served exactly what it
is served now.
publicCopyOf returns the working copy when nothing is frozen, so with no UI to
pin from yet (that arrives later in the series), nothing anyone can see changes.

Any related issues, documentation, discussions?

Closes #7939
Part of #7828. Design discussion: #7128. Stacked on #7853, which this needs and which is under
review; until that merges, this PR shows its commit too and the review here is the second one.

How was this PR tested?

22 new cases in WorkflowPublishSpec (52 in the suite, 537 in the dashboard package):

  • the published version reaching a stranger while the author keeps their own working copy;
  • opening a pinned workflow serving the whole copy — its own title, description and view — and
    the author still opening everything they have;
  • a collaborator with granted read access tracking the author's latest as they keep editing;
  • clone and duplicate taking the published copy, metadata included, while a private workflow
    still clones its working copy and a copy starts with no publish state of its own;
  • the size each case reports;
  • the revision history: a public viewer is refused while a pin is in place, a collaborator is
    not, and a public workflow with nothing pinned keeps the history it has today;
  • a public copy of a workflow that is not public being refused rather than falling through.

The history guard was checked by mutation: restoring hasReadAccess turns the leak case red,
and the case was written from a reproduction — a stranger retrieving a version of a pinned
workflow and getting back an unpublished draft.

scalafmtCheckAll clean.

Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Opus 5)

🤖 Generated with Claude Code

yangzhang75 and others added 2 commits September 16, 2026 12:36
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>
With a version pinned, a workflow has two copies: the author's working
copy and the frozen one on public show. This routes every read that
serves a viewer without granted access through the frozen copy, and
freezes the name and description with the graph.

`WorkflowPublishService.publicCopyOf` returns the three fields as a
group, so a surface cannot pick up the published graph under a title the
author has not published; `WorkflowAccessResource.hasGrantedAccess` is
the seam that decides which copy a caller gets. Granted access -- owner,
shared, project member -- keeps tracking the author's latest, because
sharing is not publishing.

Name and description freeze because they are as public as the graph: if
only the graph froze, a report about a title could be answered by editing
the title while the pinned copy still advertised it.

Routed through it: opening a workflow, the hub's read, Clone, Duplicate,
`/workflow_name`, `/workflow_description` and the size a listing shows.
A workflow that follows the author's latest -- every workflow today --
is served exactly what it is served now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • Contributors with relevant context: @aglinxinyuan
    You can notify them by mentioning @aglinxinyuan in a comment.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Benchmark changes need a look

🟢 0 better · 🔴 4 worse · ⚪ 11 noise (<±5%) · 0 without baseline

Compared against main 471e53c benchmarked on this same runner, so the delta is largely free of cross-runner hardware noise. The "7d avg" column still reflects the gh-pages dashboard. Treat <±5% as noise unless repeated.

Dashboard · Run

config throughput MB/s latency max Δ latest / 7d
bs=10 sw=10 sl=64 401 0.245 23,913/35,709/35,709 us ⚪ within ±5% / 🔴 +138.4%
🔴 bs=100 sw=10 sl=64 805 0.491 119,894/180,648/180,648 us 🔴 +32.5% / 🔴 +80.8%
bs=1000 sw=10 sl=64 940 0.574 1,067,649/1,133,100/1,133,100 us ⚪ within ±5% / 🔴 +18.1%
Baseline details

Latest main 471e53c from same runner

config metric PR latest main 7d avg Δ latest Δ 7d
bs=10 sw=10 sl=64 throughput 401 tuples/sec 414 tuples/sec 839.58 tuples/sec -3.1% -52.2%
bs=10 sw=10 sl=64 MB/s 0.245 MB/s 0.253 MB/s 0.512 MB/s -3.2% -52.2%
bs=10 sw=10 sl=64 p50 23,913 us 24,423 us 11,937 us -2.1% +100.3%
bs=10 sw=10 sl=64 p95 35,709 us 37,348 us 14,976 us -4.4% +138.4%
bs=10 sw=10 sl=64 p99 35,709 us 37,348 us 19,204 us -4.4% +85.9%
bs=100 sw=10 sl=64 throughput 805 tuples/sec 849 tuples/sec 1,096 tuples/sec -5.2% -26.6%
bs=100 sw=10 sl=64 MB/s 0.491 MB/s 0.518 MB/s 0.669 MB/s -5.2% -26.6%
bs=100 sw=10 sl=64 p50 119,894 us 116,711 us 94,033 us +2.7% +27.5%
bs=100 sw=10 sl=64 p95 180,648 us 136,306 us 99,939 us +32.5% +80.8%
bs=100 sw=10 sl=64 p99 180,648 us 136,306 us 110,148 us +32.5% +64.0%
bs=1000 sw=10 sl=64 throughput 940 tuples/sec 952 tuples/sec 1,128 tuples/sec -1.3% -16.7%
bs=1000 sw=10 sl=64 MB/s 0.574 MB/s 0.581 MB/s 0.688 MB/s -1.2% -16.6%
bs=1000 sw=10 sl=64 p50 1,067,649 us 1,050,271 us 921,191 us +1.7% +15.9%
bs=1000 sw=10 sl=64 p95 1,133,100 us 1,094,636 us 959,735 us +3.5% +18.1%
bs=1000 sw=10 sl=64 p99 1,133,100 us 1,094,636 us 988,604 us +3.5% +14.6%
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,498.69,200,128000,401,0.245,23913.38,35708.89,35708.89
1,100,10,64,20,2484.59,2000,1280000,805,0.491,119894.10,180648.14,180648.14
2,1000,10,64,20,21272.12,20000,12800000,940,0.574,1067648.78,1133100.04,1133100.04

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.57971% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.76%. Comparing base (f9b899a) to head (bf743fb).
⚠️ Report is 9 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...shboard/user/workflow/WorkflowPublishService.scala 84.00% 3 Missing and 9 partials ⚠️
...rce/dashboard/user/workflow/WorkflowResource.scala 98.24% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #8575      +/-   ##
============================================
- Coverage     92.91%   92.76%   -0.16%     
- Complexity     4884     4907      +23     
============================================
  Files          1224     1226       +2     
  Lines         51298    51534     +236     
  Branches       6322     6359      +37     
============================================
+ Hits          47664    47803     +139     
- Misses         2070     2156      +86     
- Partials       1564     1575      +11     
Flag Coverage Δ *Carryforward flag
access-control-service 71.78% <ø> (ø) Carriedforward from f67e00c
agent-service 99.32% <ø> (ø) Carriedforward from f67e00c
amber 88.61% <90.57%> (-0.40%) ⬇️
computing-unit-managing-service 54.61% <ø> (ø) Carriedforward from f67e00c
config-service 87.25% <ø> (ø) Carriedforward from f67e00c
file-service 81.53% <ø> (ø) Carriedforward from f67e00c
frontend 96.68% <ø> (ø) Carriedforward from f67e00c
notebook-migration-service 83.73% <ø> (ø) Carriedforward from f67e00c
pyamber 98.47% <ø> (ø) Carriedforward from f67e00c
workflow-compiling-service 74.09% <ø> (ø) Carriedforward from f67e00c

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Serve the pinned copy to public viewers

2 participants