Skip to content

Document safe Workflow patch retirement - #257

Open
robzienert wants to merge 1 commit into
temporalio:mainfrom
robzienert:rz/getversion-retirement-guidance
Open

Document safe Workflow patch retirement#257
robzienert wants to merge 1 commit into
temporalio:mainfrom
robzienert:rz/getversion-retirement-guidance

Conversation

@robzienert

@robzienert robzienert commented Jul 15, 2026

Copy link
Copy Markdown

Summary

Upstreams hardened versioning changes from our internal skills.

  • clarify the generic patch lifecycle without assigning language-specific behavior to the core reference
  • add a complete Go workflow.GetVersion retirement procedure covering recorded versions versus DefaultVersion, retention gates, marker placement/removal, and permanent changeID retirement
  • align Python, TypeScript, Java, .NET, and Ruby patch-retirement guidance on retention, marker-absence ambiguity, and the difference between visibility population evidence and replay compatibility evidence
  • correct Java visibility guidance for opt-in automatic upserts on 1.30+ and safe manual upserts of non-default versions

Validation

  • semantic content assertions across every language versioning reference
  • balanced Markdown fence and local-link checks
  • git diff --check
  • fresh per-language Temporal-correctness and cross-document coherence review; final re-review clean

🤖 RZ's Aimee

Comment thread references/core/versioning.md Outdated
Comment thread references/core/versioning.md Outdated
Comment thread references/go/versioning.md
@robzienert
robzienert force-pushed the rz/getversion-retirement-guidance branch from 77a2a7b to 95da572 Compare July 15, 2026 21:11
@robzienert robzienert changed the title Document safe Go GetVersion retirement Document safe Workflow patch retirement Jul 15, 2026
@robzienert
robzienert marked this pull request as ready for review July 15, 2026 21:16
@robzienert
robzienert requested a review from a team as a code owner July 15, 2026 21:16
@starfleeth

Copy link
Copy Markdown
Contributor

Thank you for this @robzienert ! Sorry about the delay. getting this reviewed ASAP.

@Sushisource Sushisource left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There's quite a lot of llm-speak in here that I think is adding more tokens than value.

I think 90% of the value here would be captured by adding a line about the difference between open and retained workflows and what that means for you if you care about querying closed workflows. There's maybe also some good bits around using the search attribute correctly.

Most of the rest of it seems to be verbose-ifying information that's already there.

I think we could cut this down to a few lines added to the core file and that'd be better.

Comment on lines +49 to +50
- Wait until executions that can return the retired version have left retention before removing that branch or raising the minimum supported version.
- Keep the Workflow's deprecation/version marker at the same deterministic location for history compatibility.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's worth mentioning you only need to wait until they've left retention if you query them after they've completed, which is somewhat rare. If you know you don't do that, then you can remove the code when they've all completed, but before they've been archived/deleted.

- After all deprecated workflows complete
- Remove patch entirely, only new code remains
- Removing the final marker is a separate operation with patching-API-specific rules beyond branch retirement.
- Retire the patch/change identifier permanently when the patching API requires it; do not reuse an identifier whose first marker call was removed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems unhelpful. It's basically saying "you can remove the marker when you should", whereas the old text tried to tell you when "should" is. Some combination of old/new would be appropriate here.

## Best Practices

1. **Check for open executions** before removing old code
1. **Wait for affected executions to leave retention** before removing old code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same comment about the difference between open & retention

# Find pre-patch workflows
temporal workflow list --query \
'WorkflowType = "OrderWorkflow" AND TemporalChangeVersion IS NULL'
Worker Deployment versions use a separate Search Attribute:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems to have dropped a bunch of context about how to do queries with TemporalChangeVersion?

The previous paragraph refers to it without explaining what it is

**Step 2: Deprecate the Patch**

Once all pre-patch Workflow Executions have completed:
After all pre-patch Workflow Executions have left retention:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same on open/retention

**Step 3: Remove the Patch**

After all workflows with the deprecated patch marker have completed, remove the `DeprecatePatch()` call entirely:
In a later deployment, after the deprecation rollout is complete and the pre-patch Workflow Executions have left retention, remove the `DeprecatePatch()` call entirely. A deprecated marker is intentionally safe to omit during replay, so marker-bearing executions do not require another retention wait:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This last sentence is hard to parse. I think it means something like "If a workflow's history contains a deprecated marker, it can safely be replayed by code without any of the old code or the patch call."

Comment on lines +129 to +130
`TemporalChangeVersion IS NULL` is reliable only when no other patch marker could make the attribute non-null. For multiple or optional patch sites, inspect all retained executions of the Workflow Type and classify the exact marker set and Event History. A zero running count identifies no immediate live blocker, but it does not prove that pre-patch histories have left retention. Replay selected histories to test compatibility; sampled replay does not prove the retained population is empty.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
`TemporalChangeVersion IS NULL` is reliable only when no other patch marker could make the attribute non-null. For multiple or optional patch sites, inspect all retained executions of the Workflow Type and classify the exact marker set and Event History. A zero running count identifies no immediate live blocker, but it does not prove that pre-patch histories have left retention. Replay selected histories to test compatibility; sampled replay does not prove the retained population is empty.
`TemporalChangeVersion IS NULL` is reliable only when no other same-id patch marker could make the attribute non-null. For multiple or optional patch sites, inspect all retained executions of the Workflow Type and classify the exact marker set and Event History. A zero running count identifies no immediate live blocker, but it does not prove that pre-patch histories have left retention. Replay selected histories to test compatibility; sampled replay does not prove the retained population is empty.

### Retirement Lifecycle

**Step 1: Add GetVersion with both code paths**
Treat `GetVersion` cleanup as replay-safety work, not ordinary dead-code removal. Removing an old branch and deleting the first marker call are separate changes with different gates.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Treat `GetVersion` cleanup as replay-safety work, not ordinary dead-code removal. Removing an old branch and deleting the first marker call are separate changes with different gates.
`GetVersion` requires care to remain replay safe. Removing an old branch and deleting the first marker call are separate changes with different gates.

For new executions, `GetVersion` returns `1` and records a marker. For replay of pre-change workflows (no marker), it returns `DefaultVersion` (`-1`).
For new executions, `GetVersion` returns `1` and records a marker. For replay of pre-change workflows with no marker for `Step1`, it returns `workflow.DefaultVersion` (`-1`). **`DefaultVersion` is not version `0`**; do not search for `Step1-0` unless the code actually recorded version `0`.

#### Step 2: Inventory and prove the old version has left retention

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This section got really big in Go for some reason, but not the others (the smaller versions seem preferable. There's a lot of redundancy with the non-language-specific file).

@robzienert

Copy link
Copy Markdown
Author

Ah sorry to waste your time on AI slop, but thanks for the feedback all the same. I'll take a second pass at this sometime this week.

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.

3 participants