CRUMB a card from devarno-cloud

Tutorial Workflow Runs

choco intermediate 4 min read

ELI5

Tutorial runs are pretend workflow runs — they look real, they run real handlers, but they are scoped to one user as a learning toy. They never graduate into the user’s actual workflows; once finished, they just stop.

Technical Deep Dive

ADR-002 (architecture/decisions/ADR-002-tutorial-workflow-runs.md) is the first cross-resource application of ADR-001’s platform-hosted pattern. Reuses the dual-discriminator (hosting_mode + lifecycle_stage) on workflow_runs.

Schema Shape

classDiagram
class WorkflowRun {
run_id
user_id
tutorial_slug
hosting_mode "user_git OR platform_template"
lifecycle_stage "playground OR live"
status "pending, running, complete, failed"
}

Differences from Sites

ADR-002 explicitly enumerates lessons folded back into the pattern doc (ADR-002:33-41):

  1. No adopted lifecycle stage. Tutorial runs don’t graduate. The 3-stage lifecycle in ADR-001 was site-specific.
  2. user_id on the resource table. Sites have only workspace_id; workflow runs needed user_id because two members of a workspace can independently run tutorials.
  3. No adoption saga. TASKSET 7’s adopt machinery does not apply; the pattern’s adoption step is optional.

One-Active-Per-Slug Enforcement

A partial unique index, not an app-layer guard:

CREATE UNIQUE INDEX ON workflow_runs (user_id, tutorial_slug)
WHERE hosting_mode = 'platform_template'
AND status IN ('pending', 'running');

Source: ADR-002:24. The DB rejects a second active tutorial run for the same (user_id, tutorial_slug). App-layer enforcement was rejected because it can race; the partial index gives a hard guarantee.

Tutorial Catalog

Three v1 tutorials, each reusing an existing choco-consumers handler (ADR-002:27-30):

Tutorial slugReused handler
check-linksbroken_links
audit-typostypo_check
style-guidestyle_guide

Catalog location: services/choco-gateway/internal/tutorial/catalog.go — kept in code rather than R2 because tutorial scenarios change with handler code (ADR-002:31-32).

3-Concurrent Cap

ADR-002 alternative B was rejected (ADR-002:48-50): “clicking the chips pops three spinners” creates an unexpected load profile. The 3-concurrent cap is both a fairness guarantee and an invariant the Prometheus alert keys on.

Key Terms

  • platform_template hosting_mode → tutorial runs are platform-owned; the user’s workspace is read-only against them.
  • partial unique index → Postgres index applied only where the WHERE clause holds; gives conditional uniqueness without a separate table.
  • catalog in code → catalog.go is checked in, not loaded from R2; tutorial code and handler code change together.

Q&A

Q: Can a tutorial run survive lifecycle_stage = adopted? A: No — the lifecycle enum for workflow runs is {playground, live} only (ADR-002:21). Tutorials don’t graduate.

Q: What stops a user starting four tutorials at once? A: The partial unique index on (user_id, tutorial_slug) WHERE platform_template AND status IN ('pending','running') blocks duplicates of the same slug; the 3-concurrent cap is the additional separate enforcement against starting all three different slugs simultaneously (ADR-002:24, 48-50).

Q: Why are tutorials not in a separate table? A: ADR-002 alternative A (ADR-002:45-46): a separate tutorial_runs table doubles the activity-feed query paths and re-introduces the bimodal data model the pattern exists to eliminate.

Examples

Adding a “broken-images” tutorial: append the entry to services/choco-gateway/internal/tutorial/catalog.go (slug, reused handler ref, demo content seed), wire the slug through the gallery UI in cho-co-web. The partial unique index already covers it; no migration needed unless adding a new column to workflow_runs.

neighbors on the map