Skip to content

Polish Ch 5 TODO 8: pre-wire executor scaffolding in compliance worker#2

Merged
MasonEgger merged 23 commits intomainfrom
init
May 3, 2026
Merged

Polish Ch 5 TODO 8: pre-wire executor scaffolding in compliance worker#2
MasonEgger merged 23 commits intomainfrom
init

Conversation

@MasonEgger
Copy link
Copy Markdown
Collaborator

Summary

  • Net delta vs main is two files in exercises/05_async_operations/ — the rest of init's history was already merged via PR Init #1's merge commit.
  • Ch 5 TODO 8 is restructured so the ThreadPoolExecutor block, prints, and await worker.run() are already in place in the exercise. The attendee now just adds three arguments (workflows, activities, activity_executor) to the existing Worker(...) call instead of rewriting the whole block.
  • README and the in-file TODO comment are tightened to match.

Test plan

  • From exercises/05_async_operations/exercise/, complete TODO 8 by adding the three lines, then run uv run python -m compliance.worker and confirm it boots.
  • Run uv run python -m payments.worker and uv run python -m payments.starter and confirm the Nexus call into ComplianceWorkflow completes.

MasonEgger and others added 23 commits April 25, 2026 14:10
Six-chapter incremental snapshot codebase that backs the Introduction
to Temporal Nexus workshop. Each chapter under exercises/ has an
exercise/ starting state and a solution/ reference; the solution of one
chapter equals the exercise of the next, so attendees can drop in at
any chapter without doing prior chapters.

Restructures the original edu-nexus-code tutorial so Ch 3 and Ch 4
teach synchronous Nexus operations only, deferring the workflow-backed
async path and Workflow Update human-in-the-loop pattern to Ch 5. Ch 6
adds lifecycle scenarios (cancellation, retryable and non-retryable
errors, circuit breaker) via failure-injection branches in the
compliance handler and a lifecycle_starter.py that exercises each
scenario.

The polyglot/java-legacy/ directory ships a Java compliance worker
that interoperates with Python callers. The Java service contract uses
@operation(name = "snake_case_name") and the data classes use
@JsonProperty("snake_case_name") so the wire format aligns with
Python's defaults; the demo lets a Python caller drive the same
workshop scenarios against the Java handler with no code change on
either side.

Single root pyproject.toml requiring Python 3.14; one shared .venv
across all chapter snapshots so attendees run uv sync once at the root
and can cd into any chapter to run code from that snapshot.
…-only

Splits the overloaded Ch 5 ("Async Operations and Updates") into two
chapters so attendees can absorb workflow-backed async operations
without simultaneously learning the Workflow Update human-in-the-loop
pattern. Ch 5 is now async-only (the handler becomes
`@nexus.workflow_run_operation`, MEDIUM-risk transactions auto-approve
with an AML monitoring note); Ch 6 layers the `review` Update and the
`submit_review` sync Nexus operation that forwards calls via
`handle.execute_update(...)` on top of Ch 5; the lifecycle chapter
(cancellation, retryable vs non-retryable errors, circuit breaker)
moves from Ch 6 to Ch 7 unchanged in scope.

Flattens the per-domain package layout by collapsing the `temporal/`
subdirectory across every chapter snapshot. `compliance/temporal/`
disappears: `activities.py`, `worker.py`, and `nexus_handler.py` move
up into `compliance/`, with `nexus_handler.py` renamed to
`service_handler.py`. `domain.py` becomes `models.py`,
`shared/nexus_service.py` becomes `shared/service.py`, and the
standalone `compliance_checker.py` / `payment_gateway.py` modules are
folded into their respective `activities.py`. Run commands shorten
correspondingly: `uv run python -m payments.worker` instead of
`uv run python -m payments.temporal.worker`.

Reduces `polyglot/java-legacy/` to a compliance-only worker. All Java
payments code (`PaymentGateway`, `PaymentStarter`,
`PaymentProcessingWorkflow`, `ReviewCallerWorkflow`, supporting
domain/activity classes) is removed; the Java side now exists solely
to serve as a drop-in replacement for the Python compliance handler at
the same Nexus endpoint. `pom.xml` drops the four named exec
executions in favor of a single default mainClass
(`compliance.temporal.ComplianceWorkerApp`), so the run command
collapses from `mvn -q exec:java@compliance-worker` to `mvn -q
exec:java`. The artifact is renamed from `decouple-monolith-solution`
to `polyglot-java-compliance` to match its new role. The Java
compliance handler also gains
`WorkflowIdConflictPolicy.USE_EXISTING`, making retried Nexus start
requests for the same `transaction_id` idempotent, and the durable
sleep is moved into the MEDIUM-risk review branch where it actually
demonstrates worker-restart resumption across an `await`.

Loosens the Python floor from 3.14 to 3.10 and bumps `temporalio` to
`>=1.24.0` so attendees on stock Python distributions can run the
workshop without first installing 3.14.

Ch 1 now ships only a `solution/` directory (no `exercise/`), since
Chapter 1's goal is to run the monolith and observe its behavior, not
to fill in TODOs. The exercise/solution pattern picks up at Ch 2. The
root README is updated to reflect this and to defer Nexus namespace
and endpoint creation (`payments-namespace`, `compliance-namespace`,
`compliance-endpoint`) out of the global setup and into Chapter 2's
Parts D and E, where they are introduced alongside the contract that
uses them.
…handling

Resolves the technical-accuracy nits surfaced by a multi-agent swarm
validation pass against the chapter chain. No runtime behavior changes;
all edits are README, docstring, and one starter except clause.

Three Medium-severity README/docstring corrections:

  - Root README pointed at "Parts D and E" of Ch 2's README, but Ch 2
    only has Parts A-D and namespace/endpoint creation lives in Parts B
    and C. Updated the cross-reference accordingly.
  - `compliance/service_handler.py` in Ch 2 (exercise + solution) and
    Ch 4 (exercise + solution) said `submit_review` "will be implemented
    in Ch 5"; the Ch 2 README, Ch 3 README, and root README all agree
    Ch 6 is the chapter that turns it into a real Update sender. Ch 5
    only introduces the workflow `submit_review` will Update. Rewrote
    the four affected docstrings to "implemented in Ch 6 once
    ComplianceWorkflow has a review Update."
  - `payments/worker.py` in Ch 5 (exercise + solution) said
    `ReviewCallerWorkflow` is "introduced in Ch 5 alongside the
    workflow-backed compliance check." `ReviewCallerWorkflow` first
    appears in Ch 6's `payments/workflows.py` (TODO 12). Reverted the
    forward reference to "introduced in Ch 6", matching the wording
    that was already correct in Ch 4's solution.

Lower-severity cleanup:

  - Ch 2's stub `service_handler.py` methods returned `None` from
    bodies typed `-> ComplianceResult`. Replaced with
    `raise NotImplementedError("TODO 2: see Chapter 3 README Part A")`
    so the stub matches the workshop's idiom (Ch 3's exercise raises
    the same error) and fails loud if anything tried to invoke it.
  - Ch 2 and Ch 3 `payments/workflows.py` docstrings introduced an
    undefined "Checkpoint 0" term and described the activities as
    "stubs" (they are the same fully-implemented Ch 1 activities).
    Adopted Ch 1's clean docstring across all four files.
  - Ch 5 README Part B now tells students to delete the
    `_check_compliance` import once the handler is workflow-backed,
    eliminating an exercise-vs-solution paper cut. Part D adds a brief
    aside noting that `schedule_to_start_timeout` and
    `start_to_close_timeout` on Nexus operations require Temporal
    Server v1.31.0 or later.
  - Ch 7 `lifecycle_starter.py` Scenario A caught bare `Exception`,
    which blunted the typed-error lesson the chapter is teaching.
    Switched to catching `temporalio.client.WorkflowFailureError` and
    print the cause chain so the underlying `nexusrpc.OperationError`
    becomes observable. Other except blocks in the file are
    intentionally broad cleanup-on-shutdown patterns and are left
    alone.
  - Ch 7's exercise and solution had drifted from Ch 6's solution on
    four pure-prose docstrings (`compliance/worker.py`,
    `compliance/workflows.py`, `payments/worker.py`,
    `payments/workflows.py`). Re-synced to Ch 6's wording so the
    boundary 6 -> 7 diff is now exactly the intentional TODO 13 add
    plus the new `payments/lifecycle_starter.py` file - nothing else.
Co-authored-by: Nikolay Advolodkin <nadvolod@gmail.com>
Co-authored-by: Nikolay Advolodkin <nadvolod@gmail.com>
Co-authored-by: Nikolay Advolodkin <nadvolod@gmail.com>
Co-authored-by: Alex Mazzeo <alex.mazzeo@outlook.com>
Co-authored-by: Alex Mazzeo <alex.mazzeo@outlook.com>
Co-authored-by: Alex Mazzeo <alex.mazzeo@outlook.com>
Co-authored-by: Alex Mazzeo <alex.mazzeo@outlook.com>
Co-authored-by: Alex Mazzeo <alex.mazzeo@outlook.com>
Co-authored-by: Alex Mazzeo <alex.mazzeo@outlook.com>
…nation

Every chapter runs the same three transaction IDs (TXN-A, TXN-B, TXN-C)
through the same `payments-namespace` and `compliance-namespace`, so a
stuck or running workflow from one chapter blocked the same ID in the
next chapter with `WorkflowAlreadyStartedError`. Re-prefixing every
constructed workflow ID with `chNN-` gives each chapter its own ID
space while keeping the IDs stable and business-meaningful (no random
suffixes — the `submit_review` handler still constructs the compliance
workflow ID by string concat from the transaction ID).

Workflow ID rename, applied symmetrically across exercise/ and solution/
for every chapter that constructs an ID:

  - `payments/starter.py` (all chapters): `f"payment-{txn.transaction_id}"`
    -> `f"payment-chNN-{txn.transaction_id}"`.
  - `compliance/service_handler.py` (Ch 5, Ch 6, Ch 7): `start_workflow`
    `id=f"compliance-{txn_id}"` -> `id=f"compliance-chNN-{txn_id}"`.
  - `compliance/service_handler.py` (Ch 6, Ch 7): `submit_review`'s
    `get_workflow_handle_for(... workflow_id=...)` lookup gets the same
    `compliance-chNN-` prefix so it still resolves the running handler
    workflow.
  - `payments/lifecycle_starter.py` (Ch 7 exercise + solution): all four
    scenarios' `id=` values, all `temporal workflow describe -w` print
    statements, and the inline banner messages updated to `ch07-`. The
    six TXN-CIRCUIT-* workflow IDs and the cancellation-side
    `compliance-ch07-TXN-CANCEL-1` reference are included.
  - `payments/review_starter.py` (Ch 6, Ch 7): docstring reference to
    the long-running compliance workflow ID updated to `compliance-chNN-`.
    The starter's own `review-TXN-B-{uuid}` ID stays UUID-suffixed
    because attendees re-run it multiple times for the same TXN-B.

README updates that quote workflow IDs:

  - Root `README.md` (separate concern caught while editing): the
    "Running a chapter" section had two stray paragraphs telling
    attendees not to run `compliance.worker`. Replaced with the actual
    Ch 1 / Ch 2 / Ch 3-7 split: Ch 1 runs only Payments (compliance
    in-process), Ch 2 is setup-only, Ch 3-7 run both workers.
  - Ch 5 README Part B: example `id=f"compliance-{...}"` snippet ->
    `compliance-ch05-{...}`.
  - Ch 6 README Parts B, D, E updated all `payment-TXN-B` and
    `compliance-TXN-B` references to `payment-ch06-TXN-B` and
    `compliance-ch06-TXN-B`. New "A note on Workflow ID design" section
    explains why the chapter prefix exists, why no UUID is appended to
    the main workflow IDs, and why `review_starter.py`'s
    ReviewCallerWorkflow ID is the deliberate exception. New
    "Re-running this chapter" caveat warns that stable IDs mean the
    starter cannot be re-run while a TXN-B compliance workflow is still
    awaiting review.
  - Ch 7 README Scenarios A, B, C, D updated all `temporal workflow
    describe`/`show` examples and the prose `payment-TXN-*` references
    to `payment-ch07-*` / `compliance-ch07-*`. Added a "Re-running
    this chapter" note pointing at the dev-server reset workaround.

Two unrelated Ch 6 solution refinements that snuck in with this pass:

  - `compliance/workflows.py` adds `assert self._review_result is not
    None` after the `wait_condition`, so the `return self._review_result`
    on the next line type-checks under strict mypy. The README Part A
    code snippet picks up the same assert.
  - `payments/workflows.py` bumps the `submit_review` Nexus operation's
    `schedule_to_close_timeout` from 10s to 60s. The 10s budget was
    fine for the in-process sync handler but was tight against the full
    Nexus round-trip (caller -> endpoint -> handler -> Update ->
    response) when running on slower laptops.
Audited every exercise/ chapter (Ch 2-7) against its corresponding
assignment.md in the workshop-nexus-intro repo. Goal: every TODO the
assignment references must have a matching marker at the exact insertion
point in the code, with no "uncomment this" patterns and no spoilery
comments that name the answer.

Authoring-standard fixes (see workshop-nexus-intro/tmp/lessons-learned.md
"Exercise file authoring standard"):

  - Rule 9 (state the goal, not the answer): rewrote TODO comments that
    named the exact decorator, API call, import, or argument literal so
    they describe what the learner is producing instead. The assignment
    text is where the answer lives. Touched markers in Ch 2 (1a-1c),
    Ch 3 (2a-2c, 3), Ch 5 (8), Ch 6 (10b, 10c, 11a, 12a), and Ch 7
    (13a-13b).
  - Rule 11 (no "Part A"/"Part B" suffixes): dropped "Chapter N, Part X"
    suffixes from every TODO. The number itself implies the chapter and
    sub-letters disambiguate locations. Cleaned up across all six
    chapters' active and breadcrumb markers.
  - Rule 3 (no "uncomment this"): removed commented-out solution code
    in Ch 2 shared/service.py (the `# @nexusrpc.service` decorator and
    `# check_compliance: nexusrpc.Operation[...]` lines). The TODO now
    asks the learner to type the line; the breadcrumb is gone.
  - Rule 6 (sub-letters per insertion point): split single TODOs that
    spanned multiple insertion points. Ch 2 TODO 1 became 1a/1b/1c
    (decorator + two operation declarations). Ch 3 TODO 2 became
    2a/2b/2c (handler decorator + two method decorators). Ch 6 TODO 10
    became 10a/10b/10c (state field + risk branch + Update method).
  - Rule 2 (`pass`, not `NotImplementedError`, for exercise stubs):
    Ch 2 service_handler stubs flipped from `raise NotImplementedError`
    back to `pass`. The runtime stub for `submit_review` between Ch 3
    and Ch 6 stays `NotImplementedError` since it ships to attendees as
    final state until Ch 6.
  - Added the missing `Registered: ComplianceNexusServiceHandler (sync
    only)` print line in Ch 3 worker.py that the assignment promises in
    its expected output but the exercise had stripped.

Six audit agents ran in parallel (one per chapter); each verified
1:1 mapping between assignment TODOs and code markers. Final state for
all chapters: every active TODO maps to an assignment step, every
breadcrumb TODO is correctly tagged with its target chapter, no
commented-out solution code remains, no spoilery hints inside method
bodies.
… polish Ch 7 lifecycle UX

Five changes, all small, propagated across the chapters they touch.

1. Rule 3 explanation now tells the truth about which arm fired
   (`compliance/activities.py` in Ch 1-7 exercise+solution, plus the
   Java `ComplianceChecker.java`).

   The Rule 3 branch fires on `amount > $10K OR international to unusual
   jurisdiction`, but the result hardcoded `"International transfer above
   $10K threshold."` That was a lie whenever only the amount arm or only
   the jurisdiction arm fired (e.g. Ch 7's `TXN-CANCEL-1` is a domestic
   $12K USD-to-USD transfer — MEDIUM by amount, not by being
   international). Now branches to three explanations covering "both
   arms fired", "amount only", "jurisdiction only", and the comment
   above the rule reads "Amount > $10K or international to unusual
   jurisdiction" instead of the old "International transfer > $10K".

   Same fix in the Java polyglot worker so the wire-equivalent handler
   keeps telling the same story as the Python side.

2. `submit_review` stub language is no longer chapter-specific
   (Ch 3 `compliance/service_handler.py`, exercise + solution).

   The Ch 3 stub used to say "Will be implemented in Ch 6" / "Ch 5
   introduces the workflow it will Update; Ch 6 fills it in." That
   leaks chapter numbering into the contract code and dates poorly
   if chapters get renumbered. Replaced with "gains a real
   implementation later in the workshop" in the docstring, the TODO
   2c hint, the inline comment, and the `NotImplementedError` body.

3. `ComplianceWorkflow.__init__` takes the request via `@workflow.init`
   (Ch 6 exercise `compliance/workflows.py`).

   Switched from a no-arg `__init__` plus a `self._request = request`
   assignment in `run()` to `@workflow.init` receiving the
   `ComplianceRequest` directly. `_request` is now non-Optional from
   construction, which simplifies typing in the Update handler the
   attendee writes for TODO 12. The solution file already used this
   pattern; this aligns the exercise scaffolding with it.

4. Lifecycle starter pauses between scenarios for inspection
   (Ch 7 `payments/lifecycle_starter.py`, exercise + solution).

   Added `pause_for_inspection(scenario)` that prompts the attendee to
   press Enter between Scenarios A→B, B→C, C→D. Without this, all four
   scenarios fire back-to-back and the next scenario's Nexus call lands
   in the Web UI before the attendee can `temporal workflow describe`
   the one they just ran. Also fixed Scenario C's banner — the old text
   called the $12K transfer "international", but the request body is
   US-to-US; it lands in MEDIUM via the amount-above-$10K arm of Rule 3.
   The new banner says so explicitly, which dovetails with change #1.

5. Java polyglot uses the `ch07` workflow-ID prefix
   (`compliance/temporal/ComplianceNexusServiceImpl.java`).

   Both the `checkCompliance` start path and the `submitReview` stub
   lookup now build `compliance-ch07-{transaction_id}` instead of
   `compliance-{transaction_id}`. The Java worker ships as a drop-in
   replacement for the Ch 6/7 Python compliance side, and the Python
   chapter already prefixes its own workflow IDs with `ch07-` (per the
   CLAUDE.md note about chapter-prefixed IDs preventing cross-chapter
   Event History collisions). Without this change, attendees swapping
   the Python compliance worker for the Java one mid-Ch 7 would land
   on a different workflow ID namespace and lose the property that
   re-running an earlier chapter doesn't trip
   `WorkflowAlreadyStartedError`.
The previous `add interactive game` commit landed `game/index.html` with
no README context, so attendees discovering the file in the repo had no
way to know what it was, how to open it, or how it related to the
chapters. This commit fills that gap.

Two changes to `README.md`:

1. Directory tree (top of the README) gains a `game/` entry alongside
   `exercises/` and `polyglot/`, with a one-line description so the
   visualizer is visible from the same place attendees look to orient
   themselves to repo layout.

2. A new "Topology sandbox (`game/index.html`)" section sits between
   the Temporal dev-server setup and the Polyglot section. It covers:

   - What the file is: a single-file browser sandbox with no
     dependencies, no server, and no connection to a live cluster.
   - How to open it (`open` / `xdg-open`).
   - What stopping a service demonstrates: workflows go to "blocked",
     not "failed", and the `Lost` counter never increments — the point
     of the visualization.
   - Framing as a Chapter 5+ orientation aid (workflow-backed async
     topology, two namespaces, Nexus endpoint as a node) rather than
     a debugging tool tied to running code, so readers understand why
     the Ch 1 monolith and Ch 3 sync handler are not represented.
   - The repo/Instruqt split: this file is the self-paced fiddle
     version checked into the code repo; the guided day-of student
     experience lives in Instruqt with a talk track authored
     separately.

No code or chapter content changed. The visualizer itself is untouched.
@MasonEgger MasonEgger merged commit 84802c5 into main May 3, 2026
3 checks passed
@MasonEgger MasonEgger deleted the init branch May 3, 2026 17:16
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.

2 participants