Skip to content

Rework challenge 01, and fix defects found walking the lab - #12

Merged
nadvolod merged 12 commits into
mainfrom
lab/challenge-01-ui-and-diagram
Aug 18, 2026
Merged

Rework challenge 01, and fix defects found walking the lab#12
nadvolod merged 12 commits into
mainfrom
lab/challenge-01-ui-and-diagram

Conversation

@nadvolod

Copy link
Copy Markdown
Collaborator

Follow-up to #9, reworking challenge 01 and fixing defects found while walking the lab. Everything here is already published to Instruqt and verified in a running sandbox.

Challenge 01 now goes: run it, see it, understand the shape, then read the code

Old order sent learners from the starter output straight into source. New order:

Run Three PaymentsSee It in the Temporal UIReview the Monolith ArchitectureFind the CouplingSee the Empty Namespace

The UI section lands on payments-namespace, gives a Workflow ID table, and opens payment-TXN-C to read its result:

{ "success": false, "status": "DECLINED_COMPLIANCE", "riskLevel": "HIGH", "error": null }

success is false, error is null, and the Workflow is Completed. That distinction is worth planting in challenge 01, because challenge 04 sends the same result across a team boundary and it should look identical: a declined payment is a business outcome, a failed Workflow is a defect.

The diagram section points at step 5 of 8 (the arrow crossing team zones inside one process) and step 8 of 8 (Compliance owning an empty Namespace).

Find the Coupling now shows both register lines rather than describing them, so nobody has to guess which two lines put two teams in one process.

Defects fixed

A run of = desynchronises Instruqt's renderer. The Compliance Worker's startup banner was in a bash,nocopy fence. The opening fence and the leading ==== line were swallowed into a collapsible divider; the trailing ==== then formed a setext <h1> from the two lines above it; every fence after that point was off by one, so the closing fence opened a code block that ate several paragraphs of prose and rendered the next ```bash,nocopy marker as literal text.

The raw markdown was correct and instruqt track validate passed, so nothing caught it. Nine other nocopy fences in the same track render fine. Replaced with one realistic output block: the Gradle task line, the stubs line, all three MultiThreadedPoller lines, and the banner text without its separators. That also removed a duplicate showing the Nexus Poller line twice.

Challenge 04 told learners to look for a banner they never write. The exercise's println is hardcoded to the monolith text, so a learner who completes the Endpoint mapping correctly still reads ComplianceActivity (monolith, will decouple). The assignment claimed a Nexus line replaced it. The verification step immediately after the hardest edit in the lab would have contradicted itself. Now the assignment says the banner is stale text and the proof comes from the run. Challenge 01 still references that banner, correctly, because there the coupling really is written down in it.

.dockerignore shipped bin/ anyway. A bare bin/ matches only <context>/bin, not decouple-monolith/exercise/bin, so the image carried a stale second copy of every .kt file including old TODO text. Fixed with **/. Context transfer went 54kB to 11kB.

Text the code already said

Per the "let the code guide the TODOs" convention:

  • Challenge 02 lost One Rule That Bites entirely. TODO 3 already explains that the runtime validates every method at Worker startup and names the exact Missing @Operation annotation failure.
  • Challenge 03 lost the checkCompliance-slow / submitReview-fast explanation, which was TODO 5 and TODO 6 nearly line for line, and A Worker only handles work it has been told about, which was TODO 7's opening sentence verbatim. Body dropped 809 to 507 words.
  • PaymentsWorkerApp.kt TODOs 10 and 11 became one short TODO 10, file 102 lines to 81. The comment is operational only; the reasoning lives in the assignment once. Exercise TODOs are now 1 to 10.
  • Removed The annotation lives in io.nexusrpc. from TODO 1 and the editor-has-no-autocomplete aside from TODO 7.

Diagram

  • The step 2 edge label ran over node text on both sides: ~300px of centred text in a 70px gutter. Edge labels now wrap on \n and that one is two short lines. The boundary wording was already in the step 5 caption.
  • The Task Queue was only in the click-to-open detail panel. It now sits on the dashed process band, which wraps both team zones, so the outline containing both teams' code is the thing labeled with one queue.

Other

Event history types are written the way the UI writes them: Activity Task Scheduled, not ActivityTaskScheduled. A learner told to find the camel-case string is scanning for something that is not on screen.

Maintenance mode is off, so attendees can launch the track. Note that instruqt track push drops maintenance: false from the file because its serializer omits false values, so the off state is an absent key rather than an explicit false.

INSTRUQT.md gained the = trap with a pre-publish grep, the assignment-wording rules, and the note that a long edge label silently covers node text because the two render in different layers.

Verification

  • Sandbox pinned at 0896ac82, linux/amd64. Verified inside the running image: one TODO marker in PaymentsWorkerApp.kt, no TODO 11, 81 lines, three uncommented registration lines intact so challenge 01 still runs the monolith, both comment removals present, diagram fixes present, bin/ gone.
  • Both Kotlin trees compile.
  • instruqt track validate clean; no = runs anywhere; every tab button verified by label rather than index arithmetic.
  • Live track matches this branch (instruqt track pull reports up to date).

Not verified by eye and worth a look after merge: the wrapped step-2 label on the Monolith Architecture tab. That fix is arithmetic against the gutter width, not observation.

This branch never modified .github/, so the merge keeps main's CI fix from #10.

🤖 Generated with Claude Code

nadvolod and others added 12 commits August 18, 2026 08:21
Challenge 01 now runs: run the payments, see them in the Temporal UI, review
the architecture, then find the coupling in code.

The UI section opens payment-TXN-C and reads its result out of the Results
panel. success is false, error is null, and the Workflow is Completed, which is
the distinction worth planting before challenge 04 sends that same result across
a team boundary: a declined payment is an outcome, a failed Workflow is a defect.

The diagram section points at step 5 of 8 (the arrow crossing team zones inside
one process) and step 8 of 8 (Compliance owning an empty Namespace).

Find the Coupling shows both register lines instead of describing them, so
nobody has to guess which two lines put two teams in one process.

Diagram fixes:

- The step 2 edge label ran over the node text on both sides. It was ~300px of
  centred text in a 70px gutter. Edge labels now wrap on \n and that one is two
  short lines, widest ~66px. The boundary wording was already in the step 5
  caption, so nothing was lost.
- The Task Queue was only in the click-to-open detail panel. It now sits on the
  dashed process band, which wraps both team zones, so the outline containing
  both teams' code is the thing labelled with one queue.

Event history types are written the way the UI writes them, with spaces.

The image is rebuilt and re-pinned because diagrams/ is baked in. Verified the
new digest carries both diagram fixes before pinning it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three things that cost time on this pass: UI event types are written with
spaces on screen, showing a line beats describing it, and an edge label wider
than the gutter silently covers node text because they render in different
layers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Maintenance mode is owner-only, which stops attendees from launching the track.
Turn it off so the workshop can run.

The CLI drops `maintenance: false` from track.yml on write, because its
serializer omits false values. The off state is therefore an absent key rather
than an explicit false. Documented in the README, because an absent key reads
like a forgotten setting.

Side effect: the create-instruqt-tutorial guardrail hook refuses to run any
command whose text contains the word it guards on while a track is out of
maintenance mode, so shipping from here needs a human to run the last git step
by hand. Also documented.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three separate cases of the same problem: prose in assignment.md restating a
TODO comment the learner is about to read anyway.

- Challenge 02 lost "One Rule That Bites" entirely. TODO 3 already explains that
  the runtime validates every method at Worker startup and names the exact
  "Missing @operation annotation" failure.
- Challenge 03 lost the checkCompliance-slow / submitReview-fast explanation,
  which was TODO 5 and TODO 6 nearly line for line, down to the 10 second cutoff
  and the duplicate-check-on-retry consequence. Replaced with a pointer to read
  both TODOs before writing either.
- Challenge 03 also lost "A Worker only handles work it has been told about",
  TODO 7's opening sentence verbatim. Replaced with which of the three
  registrations fails silently, which is the part worth knowing up front.

Challenge 03's body drops from 809 to 507 words, which also helps its 14 minute
slot.

Kept deliberately: the symptom table in challenge 03, because a lookup table for
someone already stuck is a different job from instruction, and the
declined-payment-is-not-a-failure line in challenge 01, because it is the payoff
for reading TXN-C's result in the UI.

Also removed "The annotation lives in io.nexusrpc." from TODO 1. The KDoc above
it already points at the Java docs and the Ask AI button, which is the intended
route.

Added kotlin/.dockerignore. bin/ is git-ignored but was still being copied into
the sandbox image, so the image carried a second, stale copy of every .kt file,
old TODO comments included. Confirmed present in the image before this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pins 67f4c767, which carries the io.nexusrpc removal and both diagram fixes.
Verified inside the running image rather than assumed: the TODO 1 comment no
longer names the package, the process band shows the Task Queue, and the step 2
edge label is the wrapped two-line form. Platform is linux/amd64.

The .dockerignore added in the previous commit did nothing. A bare `bin/` matches
only <context>/bin, not decouple-monolith/exercise/bin, so the image still
shipped a stale duplicate of every .kt file. The leading **/ is what makes it
recursive. Verified with a local build that is never published: bin/ gone,
src/ and gradlew and diagrams/ all intact, context transfer down from 54kB to
8.7kB.

67f4c767 predates that fix, so it still carries bin/. Harmless: code-server opens
scoped to src/main/kotlin and nothing points at bin/. It drops out on the next
rebuild.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Checksum only. The CLI recomputes it on every publish and writes it into the
local file, so committing it keeps the next delta check quiet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A run of `=` in an assignment desynchronises Instruqt's renderer. The Worker's
startup banner was in a `bash,nocopy` fence, and the leading `====` line was
swallowed into a collapsible divider along with the fence opener. The trailing
`====` then formed a setext h1 out of the two lines above it, and every fence
after that point was off by one, so the closing fence opened a code block that
ate the next several paragraphs and printed the following ```bash,nocopy marker
as literal text.

The raw markdown was correct and `instruqt track validate` passed, so nothing
caught it. Nine other nocopy fences in this track render fine; the `=` runs are
the only difference.

Replaced it with one realistic output block: the Gradle task line, the stubs
line, all three MultiThreadedPoller lines, and the banner text without its `=`
separators. That also removes the separate poller fence, which was showing the
same Nexus Poller line twice. The three pollers now appear together, so "Workflow
and Activity but no Nexus means your handler is not registered" is checkable
against what is on screen.

Also removed the note about the editor having no autocomplete from TODO 7. It
described the tooling rather than the code.

Pins 16cb8c55. Verified inside the running image: both comment removals present,
diagram fixes present, stale bin/ finally gone now that .dockerignore uses **/,
linux/amd64. Context transfer dropped 54kB to 11kB.

Documented the `=` trap in INSTRUQT.md with a pre-publish grep.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Checksum only, recomputed by the CLI on publish.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
TODOs 10 and 11 became a single TODO 10, and the file dropped from 102 lines to
81. The comment is now operational only: replace one registration with the block
given, fill the one blank, delete the other registration and its two imports.

The reasoning moved to the assignment, where prose belongs, and appears once. The
Endpoint-belongs-on-the-Worker point is the part worth keeping, because it is why
the Workflow stays reusable.

Exercise TODOs are now 1 to 10. Nothing else referenced TODO 11; the pointer in
PaymentProcessingWorkflowImpl already said TODO 10.

Fixes a real defect found while checking this. The exercise's startup banner is
hardcoded to the monolith text, so a learner who completes TODO 10 correctly
still reads "ComplianceActivity (monolith, will decouple)". Challenge 04 told
them to look for the solution's banner instead, which they never write. The
assignment now says the banner is stale text and to ignore it. Challenge 01 keeps
referencing that same banner, correctly, because there the coupling really is
written down in it.

Both trees compile. Challenge 04 body is down to 440 words.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Carries the merged TODO 10. Verified inside the running image: one TODO marker in
PaymentsWorkerApp.kt, no TODO 11, 81 lines, and the three uncommented
registration lines intact so challenge 01 still runs the monolith.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Checksum only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 18, 2026 15:42
@nadvolod
nadvolod merged commit 63e619a into main Aug 18, 2026
4 checks passed

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.

Pull request overview

This pull request updates the Kotlin Nexus “decouple-a-monolith” Instruqt track content to reflect a reordered learning flow for challenge 01 (run → observe in UI → review architecture → read code), fixes several track-rendering and packaging defects encountered during lab walkthrough, and refreshes supporting documentation/diagram presentation.

Changes:

  • Reworked Instruqt challenge assignments (especially challenge 01) to emphasize observing behavior in Temporal UI and the architecture diagram before diving into source.
  • Fixed track correctness/operability issues (maintenance mode behavior, sandbox digest repin, .dockerignore to exclude build outputs, UI event naming alignment).
  • Improved the monolith architecture diagram labeling (task queue emphasis + wrapped edge labels) and consolidated/cleaned exercise TODO guidance.

Reviewed changes

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

Show a summary per file
File Description
README.md Updates Kotlin track structure/docs, tab count, ports, and maintenance-mode guidance.
kotlin/instruqt/track.yml Removes maintenance: true and updates checksum after track changes.
kotlin/instruqt/config.yml Re-pins the sandbox image digest; confirms ports include 8090 for the diagram tab.
kotlin/instruqt/01-run-the-monolith/assignment.md Reorders and expands challenge 01 to include Temporal UI + architecture diagram first.
kotlin/instruqt/02-the-shared-contract/assignment.md Removes duplicated guidance now covered by code TODOs.
kotlin/instruqt/03-the-compliance-side/assignment.md Simplifies narrative, replaces problematic ==== banner output, and emphasizes proof signals.
kotlin/instruqt/04-the-payments-side/assignment.md Updates directions around endpoint mapping/decoupling and corrects banner expectations.
kotlin/instruqt/05-durability-and-human-review/assignment.md Aligns event-history naming with Temporal UI wording.
kotlin/diagrams/monolith-architecture.html Improves process-band labeling (task queue) and wraps long edge labels to avoid overlap.
kotlin/decouple-monolith/exercise/src/main/kotlin/shared/nexus/ComplianceNexusService.kt Refines TODO wording (removes redundant annotation package mention).
kotlin/decouple-monolith/exercise/src/main/kotlin/payments/temporal/PaymentsWorkerApp.kt Consolidates TODO guidance (TODO 10/11 → TODO 10) and clarifies endpoint mapping vs decoupling edits.
kotlin/decouple-monolith/exercise/src/main/kotlin/compliance/temporal/ComplianceWorkerApp.kt Trims non-essential commentary in TODO guidance.
kotlin/.dockerignore Ensures build outputs like **/bin/ don’t get baked into the sandbox image context.
INSTRUQT.md Documents renderer traps, assignment-wording rules, and diagram constraints for future maintenance.

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

Comment on lines +119 to +122
[main] INFO io.temporal.serviceclient.WorkflowServiceStubsImpl - Created WorkflowServiceStubs for channel: ManagedChannelOrphanWrapper{delegate=ManagedChannelImpl{logId=1, target=127.0.0.1:7233}}
[main] INFO io.temporal.internal.worker.MultiThreadedPoller - start: MultiThreadedPoller{name=Workflow Poller taskQueue="compliance-risk", namespace="compliance-namespace", identity=11418@workshop}
[main] INFO io.temporal.internal.worker.MultiThreadedPoller - start: MultiThreadedPoller{name=Nexus Poller taskQueue="compliance-risk", namespace="compliance-namespace", identity=11418@workshop}
[main] INFO io.temporal.internal.worker.MultiThreadedPoller - start: MultiThreadedPoller{name=Activity Poller taskQueue="compliance-risk", namespace="compliance-namespace", identity=11418@workshop}
Comment thread INSTRUQT.md
every publish:

```bash
grep -rn '^ *==*$' */assignment.md
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