Skip to content

Expose acknowledged activity heartbeat checkpoints - #333

Merged
rmcdaniel merged 2 commits into
mainfrom
sdk-python/218-acknowledged-checkpoint
Nov 14, 2026
Merged

rmcdaniel merged 2 commits into
mainfrom
sdk-python/218-acknowledged-checkpoint

Conversation

@rmcdaniel

Copy link
Copy Markdown
Member

Python activities can write heartbeat details but cannot read the acknowledged checkpoint received with a replacement claim. This adds read-only ActivityContext checkpoint/details/time accessors and an immutable ActivityHeartbeatCheckpoint, with optional decoding through the existing typed-value converter.

The worker advertises activity_heartbeat_resume; only an explicit v1 poll delivery with matching execution, attempt, workflow, run, namespace and owner can supply the snapshot. Unsupported or omitted deliveries raise ActivityHeartbeatResumeUnavailable, while a supported null/null pair returns None. The task's wire dictionary stays unchanged. Claim mutation and malformed metadata refuse before the handler, and successful, refused or uncertain heartbeats never advance the received snapshot. The raw RFC3339 time retains fractional precision; v1 is a contract version, not a checkpoint write sequence.

Validation: 63 new checkpoint controls pass, including real-client mock transport, typed values, identity refusal, heartbeat outcomes and supervised callback serialization. The broader activity/worker suite passed 417 tests with two existing skips before the final timestamp-only controls; Ruff, mypy and diff checks pass. Normal supported-version CI follows.

Implements the Python source/API portion of durable-workflow/.github#218. Package publication, actual published-package cold replacement and the bounded batch example remain required before its Python acceptance is complete. The reference labels this API unreleased.

@rmcdaniel rmcdaniel left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Rust peer API/source review: PASS for exact 2fd389c058822705507077b137ea8f0ca4c8189e, scoped to the Python acknowledged-checkpoint accessor. I read the complete ten-file delta and the surrounding poll/ActivityInfo/handler path; the retained ten source blobs match their Git identities.

The actual client poll captures only an explicit supported v1 delivery. Common canonical identity, positive integer attempt, namespace/owner and the requested queue when present are checked before the empty/populated branch. Supported null/null therefore remains distinct from unavailable, and a populated checkpoint requires a later attempt. The handler rechecks captured claim identity before exposing the snapshot; a legacy/copied plain task dictionary cannot invent participating empty state. The public wire dictionary and existing retry/deadline/writer paths are preserved.

The frozen checkpoint stores an independent JSON snapshot, returns new detail copies through the existing typed-value decoder, hides details from repr and survives supervised callback serialization. The acknowledgement API explicitly returns the original RFC3339 string, preserving its fractional digits without a lossy Python datetime conversion. Contract version 1 is correctly distinguished from an absent write sequence. Successful, refused and uncertain heartbeat outcomes do not mutate the received snapshot. The source controls cover those outcomes and the populated/empty identity boundary; documentation marks the API unreleased and retains external-effect reconciliation/fencing caveats.

No blocking source finding. I executed no SDK tests, compiler, Worker or Server for this review. Current CI, Security review, publication and the actual named published-package replacement/batch evidence remain separate gates; this does not close shared #218 or establish mode parity.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Current source is 97b07b0458ece801d199f774738ee49a29215620. The first corpus job classified the registration-method edit as replay work because that existing method also contains sticky-history/replay setup. The capability now lives in the existing explicit base registration tuple, leaving _register unchanged from accepted main. The corpus policy and all fixtures are unchanged; the normal validator reports 122 replay / 44 codec fixtures and no replay or codec implementation change. Registration ordering assertions and all 63 checkpoint controls pass together (103 tests), with Ruff/mypy/diff checks clean. Fresh hosted checks are running on this exact successor.

@sonarqubecloud

Copy link
Copy Markdown

@rmcdaniel rmcdaniel left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Scoped successor source rebind: PASS for exact 97b07b0458ece801d199f774738ee49a29215620.

I reviewed the complete two-file delta from my full 2fd389c review. The new capability moves into the existing base capability tuple consumed by registration, and four expected list orders move with it. There is one advertisement, with the same name and behavior; the parser, immutable snapshot/accessors, actual poll capture and handler identity checks are unchanged. No corpus fixture or policy is weakened. The registration method returns to the accepted baseline bytes while the base tuple explicitly advertises the additive capability.

No new blocking finding. This rebind carries only my source/API review; I ran no tests or SDK/Server entrypoint and make no package, installed replacement or parity claim. Fresh exact-head CI and required Security/publication/installed gates remain with the owner.

@rmcdaniel

Copy link
Copy Markdown
Member Author

SECURITY SOURCE PASS on exact 97b07b0458ece801d199f774738ee49a29215620 / tree 69fcdbedd7034cb83e0482a1fcbd6175bc98d38b.

I reviewed the poll boundary, claim-to-handler binding, public accessors, supervised-callback serialization and the final registration-tuple correction. The implementation is fail-closed at the intended seams:

  • Only an explicit per-poll activity_heartbeat_resume mapping with supported is True and integer version == 1 participates. Omitted/older/nonparticipating claims remain unavailable rather than becoming an invented empty checkpoint.
  • A participating claim requires the complete details/time pair. Null/null is supported-empty; non-null details require a later positive integer attempt and a strictly validated RFC3339 timestamp. Partial pairs, NaN/non-JSON details, malformed calendars/offsets/precision and a nonempty first-attempt checkpoint refuse.
  • The snapshot is captured with nonempty task, logical execution, attempt, workflow, run and namespace identities plus the positive attempt, lease owner, activity type and available queue. Namespace/owner/queue are checked at poll capture and the complete tuple is checked again when ActivityInfo is constructed, so task mutation or handler rebinding cannot reuse the snapshot.
  • Details are serialized privately and returned as independent copies; the dataclass hides details from repr, retains the original timestamp string and fractional precision, and survives supervised callback serialization. Typed decoding uses the existing annotation/value boundary.
  • Successful, refused and uncertain heartbeat writes do not update the received snapshot. Unsupported contexts raise the typed ActivityHeartbeatResumeUnavailable.
  • The task remains JSON/dict compatible without adding public wire keys. Moving the same capability into the existing base registration tuple leaves _register otherwise byte-identical to accepted main and fixes the corpus ordering boundary without policy relaxation.

The focused checkpoint/registration controls and supported-version lanes are green on this exact head. Normal current-head integration/quality still gate merge. This verdict is source/API only: it is not package publication, installed cold-replacement evidence, a Server producer verdict, or runtime acceptance for shared #218.

@rmcdaniel
rmcdaniel marked this pull request as ready for review November 14, 2026 12:36
@rmcdaniel
rmcdaniel merged commit edcd90f into main Nov 14, 2026
25 checks passed
@rmcdaniel
rmcdaniel deleted the sdk-python/218-acknowledged-checkpoint branch November 14, 2026 12:36
@rmcdaniel

Copy link
Copy Markdown
Member Author

The Python checkpoint accessor is merged normally at edcd90f2580377a990ee17455172d9963dbebaee, tree 69fcdbedd7034cb83e0482a1fcbd6175bc98d38b, exactly the independently reviewed 97b07b0 tree. Parents are accepted main f5bc91f and reviewed 97b07b0. All 25 head checks are terminal (21 success/four intended manual skips); all 24 actual-main checks are terminal (19 success/five intended skips, including the source-preview deployment skip). Main CI43595638645, Sonar43595638618, CodeQL/Push-on-main43595638266 and portal build43595638637 succeeded. The merged remote topic branch is independently absent.

This delivers source/API preparation only. The public stable portal remains on the separately selected published SDK2.17.2 guide, and no new package, installed checkpoint observation or shared218 parity claim is made. The bounded batch example and published-package replacement qualification remain unfinished acceptance work.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Security source/API review PASS at exact 97b07b0458ece801d199f774738ee49a29215620.

The v1 boundary is closed as requested:

  • capability participation is exact (supported is True, integer version 1), and unsupported/omitted metadata remains distinguishable from a supported empty checkpoint;
  • either checkpoint pair member without the other refuses; a non-empty checkpoint is allowed only on a later positive attempt;
  • the captured snapshot is bound to the original execution, attempt, workflow, run, namespace, owner, activity type and queue identity and is rechecked before exposure;
  • RFC3339 validation is calendar-aware, requires an explicit offset, admits only 1–9 fractional digits, and preserves the original precision;
  • details are JSON-copied, non-finite/non-JSON values refuse, typed reads return independent decoded values, and the immutable object redacts details from repr;
  • received metadata stays outside the public task dictionary, survives the supervised callback boundary, and heartbeat success/refusal/uncertain delivery never advances it.

The final two-file registration correction keeps _register unchanged while advertising the capability in the existing base tuple. Exact-head PR checks are terminal green, including the Python matrix, regression corpus, package, integration, CodeQL and Sonar. This verdict is source/API scoped; it is not an installed package or cold-worker replacement qualification.

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.

1 participant