Skip to content

feat(workflow): freeze a pinned version as the public copy - #7853

Open
yangzhang75 wants to merge 1 commit into
apache:mainfrom
yangzhang75:pin/2-service
Open

yangzhang75 wants to merge 1 commit into
apache:mainfrom
yangzhang75:pin/2-service

Conversation

@yangzhang75

@yangzhang75 yangzhang75 commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

A public workflow follows the author's latest content, as publishing has always done: every
save reaches the Hub immediately. This adds the other state — the author pins the version
they have now, and what the public sees stops moving until they pin again.

  • WorkflowPublishService owns the two states and the moves between them: publish,
    pinLatest, unpin, unpublish, statusOf. is_public stays the on/off switch;
    published_content is the pin, NULL while following.
  • A pin freezes everything on public show — the graph, the title, the description and the
    view the workflow opens in — because a copy that froze only its graph would still advertise
    a title nobody published. The default view matters because a form's definition rides inside
    the content: serving the author's live preference over a frozen graph would open a form on a
    copy that has none.
  • A pin is one statement. Each column is copied from its own row (published_content = content
    and so on) rather than from a workflow read a moment earlier, so there is no window in which the
    author's next save lands and the pin freezes the version before it -- which would have left them
    reading "you have unpublished changes" the instant after they pinned.
  • Three endpoints:
Endpoint Does
POST /workflow/pin/{wid} freezes the author's current version as the public copy; called again, moves the pin forward
DELETE /workflow/pin/{wid} drops the pin, back to following the latest
GET /workflow/publish-status/{wid} published, pinned, and whether a pin is holding edits back
  • 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.

  • One writer for the publish columns. Two paths could still write them by accident, because
    each read a whole row (or took a whole Workflow from the client) and wrote it all back:

    • a rename or description edit wrote every column from a row it had read a moment
      earlier, so anything landing in between was silently rewritten to what that read had seen:
      a publish undone, a pin dropped, or — worst — a workflow the author had just unpublished
      put back on public show under its frozen copy. Each now writes only its own column.
    • creating a workflow clears the publish columns, so a request body cannot seed a
      published copy of its own choosing.

    A plain save was the third, and fix(workflow): a plain save no longer clobbers is_public #8498 has since narrowed it on main for its own reasons,
    so nothing is needed here — the tests below still pin the behaviour from this feature's side.
    Together with /set-default-view, which already wrote only its column, every endpoint now
    writes what it owns and nothing else: outside creation, WorkflowPublishService is the only
    writer of is_public and the frozen copy.

Nothing on screen changes. No read path consults the pinned copy yet and there is no UI:
every workflow stays in the following state it is in today. The endpoints answer, and nothing
calls them.

Any related issues, documentation, discussions?

Closes #7938
Part of #7828. Design discussion: #7128. Schema: #7851.

How was this PR tested?

32 cases in WorkflowPublishSpec:

  • the state machine — follow → pin → re-pin → unpin → unpublish → re-publish, and that a
    re-publish does not resurrect the previous pin;
  • what a pin freezes — the name, the description and the default view alongside the content,
    and that each of them moving afterwards is reported as an unpublished change;
  • the guards — no write access, not published, and a workflow that does not exist;
  • that a create cannot inject publish columns, and that a save, a collaborator's save and a
    rename all leave the publish state alone;
  • that a publish or an unpublish landing while a rename is in flight survives it, and that a save
    landing while a pin is in flight is the version that gets pinned. These three drive the
    interleaving off the statement itself rather than off a thread, so the ordering is the same on
    every run;
  • the ordinary states that are easy to forget: content that is not valid JSON is reported as
    drift rather than throwing, a workflow with no description pins like any other,
    unpinning one that is already following is a no-op rather than an error, and publishing one that
    is already public leaves its pin alone.

hasUnpublishedChanges compares 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.

The narrowed write paths were checked by mutation: restoring is_public to the save statement
turns three cases red, one of them on the CHECK constraint itself, and restoring the
read-modify-write to the rename path turns its two interleaving cases red — one showing the
publish reverted, the other showing the unpublished workflow back on public show. Restoring the
read-then-write to the pin turns the third red, showing the pin frozen one save behind.

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

Generated-by: Claude Code (claude-opus-5)

@github-actions github-actions Bot added engine ddl-change Changes to the TexeraDB DDL labels Aug 23, 2026
@github-actions

github-actions Bot commented Aug 23, 2026 •

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.

@codecov-commenter

codecov-commenter commented Aug 23, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.46154% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.59%. Comparing base (b43d465) to head (2d8f489).

Files with missing lines Patch % Lines
...shboard/user/workflow/WorkflowPublishService.scala 84.48% 1 Missing and 8 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #7853      +/-   ##
============================================
- Coverage     92.60%   92.59%   -0.02%     
- Complexity     5044     5049       +5     
============================================
  Files          1252     1253       +1     
  Lines         53578    53644      +66     
  Branches       6671     6691      +20     
============================================
+ Hits          49615    49670      +55     
- Misses         2318     2320       +2     
- Partials       1645     1654       +9     
Flag Coverage Δ *Carryforward flag
access-control-service 77.38% <ø> (ø) Carriedforward from b43d465
agent-service 99.16% <ø> (ø) Carriedforward from b43d465
amber 88.06% <88.46%> (-0.02%) ⬇️
computing-unit-managing-service 60.41% <ø> (ø) Carriedforward from b43d465
config-service 87.37% <ø> (ø) Carriedforward from b43d465
file-service 81.53% <ø> (ø) Carriedforward from b43d465
frontend 96.59% <ø> (ø) Carriedforward from b43d465
notebook-migration-service 83.73% <ø> (ø) Carriedforward from b43d465
pyamber 98.58% <ø> (ø) Carriedforward from b43d465
workflow-compiling-service 74.09% <ø> (ø) Carriedforward from b43d465

*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.

@github-actions

github-actions Bot commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Benchmark changes need a look

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

Compared against main b43d465 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 369 0.225 25,852/35,051/35,051 us 🔴 -13.1% / 🔴 +159.3%
🔴 bs=100 sw=10 sl=64 766 0.468 127,304/172,229/172,229 us 🔴 +9.0% / 🔴 +83.6%
⚪ bs=1000 sw=10 sl=64 902 0.551 1,096,399/1,235,078/1,235,078 us ⚪ within ±5% / 🔴 +36.5%
Baseline details

Latest main b43d465 from same runner

config metric PR latest main 7d avg Δ latest Δ 7d
bs=10 sw=10 sl=64 throughput 369 tuples/sec 424 tuples/sec 963.37 tuples/sec -13.0% -61.7%
bs=10 sw=10 sl=64 MB/s 0.225 MB/s 0.259 MB/s 0.588 MB/s -13.1% -61.7%
bs=10 sw=10 sl=64 p50 25,852 us 23,355 us 10,998 us +10.7% +135.1%
bs=10 sw=10 sl=64 p95 35,051 us 34,965 us 13,515 us +0.2% +159.3%
bs=10 sw=10 sl=64 p99 35,051 us 34,965 us 16,850 us +0.2% +108.0%
bs=100 sw=10 sl=64 throughput 766 tuples/sec 781 tuples/sec 1,235 tuples/sec -1.9% -38.0%
bs=100 sw=10 sl=64 MB/s 0.468 MB/s 0.477 MB/s 0.754 MB/s -1.9% -37.9%
bs=100 sw=10 sl=64 p50 127,304 us 125,285 us 87,833 us +1.6% +44.9%
bs=100 sw=10 sl=64 p95 172,229 us 157,963 us 93,795 us +9.0% +83.6%
bs=100 sw=10 sl=64 p99 172,229 us 157,963 us 103,718 us +9.0% +66.1%
bs=1000 sw=10 sl=64 throughput 902 tuples/sec 913 tuples/sec 1,273 tuples/sec -1.2% -29.1%
bs=1000 sw=10 sl=64 MB/s 0.551 MB/s 0.557 MB/s 0.777 MB/s -1.1% -29.1%
bs=1000 sw=10 sl=64 p50 1,096,399 us 1,089,421 us 861,707 us +0.6% +27.2%
bs=1000 sw=10 sl=64 p95 1,235,078 us 1,213,710 us 904,523 us +1.8% +36.5%
bs=1000 sw=10 sl=64 p99 1,235,078 us 1,213,710 us 931,473 us +1.8% +32.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,541.48,200,128000,369,0.225,25851.61,35051.28,35051.28
1,100,10,64,20,2609.43,2000,1280000,766,0.468,127303.58,172228.55,172228.55
2,1000,10,64,20,22167.25,20000,12800000,902,0.551,1096398.67,1235078.38,1235078.38

@yangzhang75
yangzhang75 force-pushed the pin/2-service branch 3 times, most recently from a3c9eee to a70c881 Compare September 3, 2026 21:09
@github-actions github-actions Bot removed the ddl-change Changes to the TexeraDB DDL label Sep 3, 2026
@yangzhang75
yangzhang75 marked this pull request as ready for review September 3, 2026 22:14
@yangzhang75
yangzhang75 marked this pull request as draft September 3, 2026 22:15
@yangzhang75
yangzhang75 force-pushed the pin/2-service branch 2 times, most recently from 3f61548 to fdd8454 Compare September 4, 2026 05:33
@yangzhang75
yangzhang75 marked this pull request as ready for review September 4, 2026 17:50
@yangzhang75

Copy link
Copy Markdown
Contributor Author

/request-review: @mengw15

@mengw15
mengw15 requested a balanced review from Copilot September 4, 2026 18:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Adds a publish “pinning” state for workflows so public content can either follow the author’s latest edits or freeze to an explicitly pinned snapshot, with new API endpoints and a dedicated service to prevent accidental clobbering of publish-related columns.

Changes:

  • Introduces WorkflowPublishService implementing publish/pin/unpin/unpublish/status state transitions.
  • Adds /pin/{wid} (POST/DELETE) and /publish-status/{wid} (GET) endpoints and tightens write paths to update only owned columns.
  • Adds a comprehensive WorkflowPublishSpec covering state machine behavior and interleaving/race scenarios.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala Adds pin/status endpoints and replaces read-modify-write updates with single-field UPDATEs to avoid clobbering publish state.
amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowPublishService.scala New service owning publish/pin state transitions and drift detection vs pinned copy.
amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowPublishSpec.scala New test suite validating pinning semantics, drift detection, access guards, and update interleavings.
Suppressed comments (1)

amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowResource.scala:1

  • This update path silently succeeds when the workflow row doesn’t exist (UPDATE returns 0), whereas the previous DAO fetch would fail fast. That can lead to endpoints returning 200/204 even though nothing was updated (e.g., if a workflow is deleted between access check and update). Capture the execute() row count and throw NotFoundException when it is 0.
/*

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Pinning can race with unpublishing and inadvertently make a private workflow public again.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (2)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Concurrent unpublishing can let an unpin request succeed after the workflow has become private.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (1)

.set(WORKFLOW.PUBLISHED_DEFAULT_VIEW, null.asInstanceOf[DefaultViewEnum])
val statement =
if (alsoUnpublish) cleared.set(WORKFLOW.IS_PUBLIC, java.lang.Boolean.FALSE) else cleared
statement.where(WORKFLOW.WID.eq(wid)).execute()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also fixed, and it is the mirror of the previous one — thanks.

clearPin now carries the condition in its own statement for the unpin path (WHERE wid = ? AND is_public), and unpin tells a missing workflow from a private one the same way pinLatest does. Unpublishing deliberately keeps no such condition: taking down what is already down is a no-op, not an error.

Covered by an interleaving test that mirrors the pin one — a /private committed between the guard and the update now answers 400 and leaves the workflow down, instead of reporting "unpinned, following latest" about something no longer on show.

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>
@yangzhang75

Copy link
Copy Markdown
Contributor Author

/request-review: @aglinxinyuan

@github-actions
github-actions Bot requested a review from aglinxinyuan October 7, 2026 01:35

This branch has not been deployed

No deployments
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.

Freeze a pinned version as the public copy

4 participants