Skip to content

DM E2E — diagram image-generation prompts

DM E2E — diagram image-generation prompts

Section titled “DM E2E — diagram image-generation prompts”

Per-section prompts for generating the DM execution diagrams as separate images (one concept per image — splitting the original busy single-canvas diagram for clarity).

All prompts reflect the post-ADR-0001 architecture (../decisions/0001-drop-pd1-sqs-handoff.md): PD-1 posts the notification gateway directly at stage 7; there is no SQS queue and no separate PD-2 consumer. The companion narrative is ../dm-execution-e2e.md.

Facts are pinned to the shipped code/spec so a regenerated image stays accurate:

  • Audit row: PK dm_run_id (uuid4), SK user_id, dedupe key event_id = dm_run_id:user_id.
  • Outcomes: dispatched, skipped_no_candidates, skipped_eligibility, skipped_min_items_gate, skipped_flag_off, skipped_kill_switch, skipped_unsupported_copy, failed (+ PD-2 skipped_weekly_cap / skipped_quota / dropped_no_devices).
  • Gateway endpoint: POST /v1/gateway/notify. Stage 2 = CCS enrichment (batched).
  • Dispatch row fields: notification_id (was sqs_message_id) + scheduled_at.

Status legend (as of 2026-06-17, matches the narrative doc): PD-3 registry + PD-1 executor core (pipeline, audit, CCS) are [LIVE] on main. The full PD-1 adapter set is [REVIEW-READY] — open, green, mergeable PRs #228–#237, not yet merged: runner adapters (PR-G #228), flag checker (PR-H #229), Eppo resolver (PR-I #230), cron concretes (PR-J #231), dispatch send_time/cap/content (PR-L1 #232 / PR-L2 #233 / PR-K #235), futurefire queue (PR-M #234), unified-worker composition root + candidate-query binding + Observer (PR-F #237). Kafka mode and the one-off-fire HTTP route are [PLANNED]. When rendering status chips, use these three states; do not mark adapter pieces “LIVE”.

Recommended set: 1 overview + 7 detail images. Generate each prompt independently.


Prompt 1 — Overview (the only cross-cutting image)

Section titled “Prompt 1 — Overview (the only cross-cutting image)”
A clean horizontal architecture diagram titled "DM Execution — End-to-End (Overview)".
Three left-to-right swimlanes: GREEN "PD-3 Type Registry [LIVE]", BLUE "PD-1 Executor
[CORE LIVE · adapters REVIEW-READY]", and a thin PURPLE inset INSIDE the blue lane labeled
"PD-2 dispatch (inline, not a separate service)".
Flow: PD-3 (dm/types/*.yaml → in-process registry) --read in-process--> PD-1 (three firing
modes converge to one FireEvent → a 7-stage pipeline). Show stage 7 of the pipeline as
"DISPATCH (inline)" inside the purple inset. From stage 7, ONE solid arrow labeled
"POST /v1/gateway/notify (HTTPS, direct)" goes to a box "notification-service gateway
(Growth-owned): dedup · quota · quiet-hours · scheduling" → "APNs / FCM → push / in-app".
Separately from the pipeline, a SYNC arrow to a DynamoDB cylinder "{env}-dm-schedule-events"
→ dashed "DDB Streams" → "Snowflake (PLT-629 funnel)".
Wrap the whole thing in one labelled boundary box "cmd/unified-worker — single Go binary,
one LifecycleManager" to make clear the registry, executor, inline dispatch, and Valkey cap
all run in ONE process (the only external surfaces are the DynamoDB tables + the gateway).
IMPORTANT: there is NO SQS queue and NO separate PD-2 consumer between PD-1 and the gateway —
do not draw a queue. Add a small callout box: "ADR-0001: the PD-1→PD-2 SQS handoff was
removed; PD-1 posts the gateway directly and the gateway owns dedup/quota/quiet-hours/
scheduling." Add a faint status-chip legend (GREEN=live on main, BLUE=review-ready PR stack
#228–#237, GREY=Growth-owned). Minimal, lots of whitespace, flat modern icons, professional.

Prompt 2 — PD-3: the registration & its fields

Section titled “Prompt 2 — PD-3: the registration & its fields”
A simple diagram titled "PD-3 — DM Type Registry [LIVE]". On the left a YAML file icon
"dm/types/weekly-forecast.yaml" with an arrow "Loader parses at startup" into a cylinder
"registry.Registry (in-process), indexed by type_id". On the right a two-column table
"Registration field → where it's used":
trigger (cron|kafka) → firing mode; candidate_query → PD-1 stage 1; eligibility callable
→ PD-1 stage 3; ranking (optional) → PD-1 stage 4; min_items_gate → PD-1 stage 3;
copy (static Jinja | llm_prompt) → PD-1 stage 5; send_time → stage 7 dispatch
(→ scheduled_time); priority, frequency_cap → stage 7 dispatch (gateway tie-break + cap);
idempotency_key_template → stage 7 (rendered → request_id; gateway owns 24h dedupe);
experiment, flag → stages 6–7 (fire-time check, no dispatch re-check); analytics_labels,
reporting_metrics → carried into the producer=pd2 audit → PS-5 → PLT-629.
Bottom callout: "Single source of truth for the rollout triple (flag, experiment, analytics).
Add one YAML file to ship a new DM type — no executor code change." Green accent, clean.

Prompt 3 — PD-1: firing modes → FireEvent → 7-stage pipeline

Section titled “Prompt 3 — PD-1: firing modes → FireEvent → 7-stage pipeline”
A detailed diagram titled "PD-1 Executor — Firing a DM [CORE LIVE]", blue accent.
Top row: three firing-mode boxes with status chips — "CRON TICK (cron loop per type)
[review-ready PR-J #231]", "KAFKA MESSAGE (DM trigger topic) [planned]", "ONE-OFF FIRE
(future-fire queue) [queue review-ready PR-M #234; route planned]" — all converging with
arrows into a single wide box "ONE FireEvent per (user, instant)" listing fields:
DMRunID (uuid4), SessionID (uuid4), UserID (from candidate row or trigger payload — NEVER
from HTTP), TypeID, TriggerKind (cron|kafka|one_off), FireTime, EventPayload (nil for cron),
OneOffFireID.
Below it, a numbered left-to-right 7-stage pipeline: 1 Candidate Selection (run
candidate_query → rows of users), 2 CCS Enrichment (batched), 3 Eligibility & min_items_gate,
4 Ranking (optional), 5 Copy Render (Jinja → copy+data), 6 Flag Re-check (per-user cohort,
fire-time), 7 DISPATCH (inline — see separate stage-7 image).
Three outputs branch from the end: "SYNC ScheduleEvent audit write (durability guarantee)",
the stage-7 dispatch to the gateway, and a thin side-channel to an "Observer (structured
logs + OTel counters: dm_schedule_event_put_errors_total, dm_candidate_query_errors_total)
[PR-F #237]".
Add a small inset box near stage 1: "Candidate-query binding (cron): Weekly Forecast's
candidate_query is kind:callable, bound into the symbol table AT WORKER STARTUP (PR-F #237)
because it needs the live Neo4j client + experiment-membership store; registry loads with
WithCallableResolution() so an unbound ref fails fast at startup."
Small note: "50k candidates = 50k FireEvents = 50k audit rows; worker pool of 32, ~80s drain.
Writes spread across DDB partitions by user_id." Clean, professional, flat icons.

Prompt 4 — The ScheduleEvent audit row & analytics path

Section titled “Prompt 4 — The ScheduleEvent audit row & analytics path”
A focused data-model diagram titled "ScheduleEvent Audit — the durability guarantee",
amber/shared accent. Center: a DynamoDB table card "{env}-dm-schedule-events (on-demand)" with:
PK = dm_run_id (uuid4); SK = user_id; event_id = dm_run_id:user_id (Snowflake dedupe key);
fields type_id, session_id, trigger_kind, fire_time, outcome, reason, producer, ttl(90d),
and (highlighted as set by dispatch) notification_id + scheduled_at.
A boxed list "outcome ∈ { dispatched, skipped_no_candidates, skipped_eligibility,
skipped_min_items_gate, skipped_flag_off, skipped_kill_switch, skipped_unsupported_copy,
failed }" with a note "+ PD-2 dispatch adds skipped_weekly_cap, dropped_no_devices".
Show TWO writers to the same row: "PD-1 fire-time write (producer=pd1): keys + fire_time"
and "stage-7 dispatch UpdateItem (producer=pd2): outcome + notification_id + scheduled_at".
Arrow from the table via "DDB Streams (NewImage)" to "MSK Connect → S3 → Snowflake
DM_SCHEDULE (15-min merge)" then to "PLT-629 funnel".
Callouts: "One row per (dm_run_id, user_id), written synchronously before the fire returns —
the write IS the durability guarantee." "scheduled_at lets the funnel answer: fired Mon
00:00 UTC → scheduled for delivery Mon 08:00 user-local." Clean, precise, monospace for keys.

Prompt 5 — Stage 7: the inline dispatch step (NEW under ADR-0001)

Section titled “Prompt 5 — Stage 7: the inline dispatch step (NEW under ADR-0001)”
A focused diagram titled "PD-1 Stage 7 — Dispatch (inline, ADR-0001)", purple accent.
Top: a box "HandoffMessage (assembled by stages 1–6)". A vertical flow of dispatch sub-steps:
1) "Per-type weekly cap — Valkey Lua atomic reserve (dm_weekly:{user}:{type})";
2) "Map → notification-service NotifyRequest (idempotency_key→request_id, type_id→notif_type,
bff_payload→content)";
3) "send_time → scheduled_time (immediate / scheduled MON 08:00 tz:user / window)";
4) "POST /v1/gateway/notify via notification-service/pkg/client SDK — inline bounded retry
on 5xx/408".
From the POST, branch to outcomes: "202 → dispatched (persist notification_id + scheduled_at;
keep cap slot)", "429 → skipped_quota (release slot)", "dropped:no-devices →
dropped_no_devices (release slot)", "5xx after retries → failed (release slot)".
End with a dashed/greyed step "audit UpdateItem (producer=pd2) on the same (dm_run_id,user_id)
row — FOLLOW-UP, not yet built; until it lands PD-1's producer=pd1 fire-time row is the
system of record per fire".
Mark sub-steps 1–4 as implemented (review-ready: send_time PR-L1 #232, cap PR-L2 #233,
content PR-K #235).
Right-side callout: "No SQS, no separate consumer, no dispatch-time re-validation — kill-switch
and cohort flag were checked at fire time (stage 6). The gateway holds deferred (scheduled)
sends in its own Valkey scheduler." Clean, precise, monospace for field names.

Prompt 6 — The notification-gateway pipeline (Growth-owned)

Section titled “Prompt 6 — The notification-gateway pipeline (Growth-owned)”
A diagram titled "notification-service Gateway — 12 guardrails (Growth-owned)", grey accent.
A single inbound arrow "POST /v1/gateway/notify (from PD-1 stage-7 dispatch)" into a tall box
listing the 12 ordered steps: 1 idempotency (request_id 24h), 2 expiration, 3 device presence,
4 platform device, 5 opt-out, 6 quiet hours (reschedule to 7am if not time_sensitive),
7 consolidation, 8 daily quota (100/user/day), 9 collision resolution (score-based eviction),
10 schedule to Valkey sorted set, 11 increment quota, 12 mark processed → 202.
Side note: "<100ms p99; Valkey-bound hot path." A response box: "202 Accepted (notification_id,
scheduled_at) → dispatched | 429 → skipped_quota | dropped:no-devices → dropped_no_devices |
400 → failed_schema | 5xx → inline retry by the dispatcher".
Callout: "The gateway OWNS dedup, quota, quiet-hours, and scheduling — PD-1 does not
re-implement them (ADR-0001)." Clean, professional.

Prompt 7 — Beyond the gateway: delivery to device

Section titled “Prompt 7 — Beyond the gateway: delivery to device”
A simple linear diagram titled "Beyond the Gateway — Delivery [Growth-owned]", neutral/grey
accent. Left-to-right: "PD-1 stage-7 dispatch" --POST /v1/gateway/notify--> "notification-
gateway (202; if scheduled_time set, held in Valkey scheduler until the instant)" →
"notification-dispatcher (polls Valkey every 2–60s)" → "notification-sender" forking to
"APNs → Apple Push → iOS device" and "FCM → Google → Android device". A small "device-registry"
box feeds device tokens into the sender. Dashed return arrow "delivery/failure receipts →
PS-5 / PLT-629 analytics".
Two callouts: "The push is a pointer; the durable artifact is the 90-day in-app EPISODE
(PLT-372)." "notif_type = type_id verbatim (semantic identity), NOT a UI surface;
presentation.surface is proposed-not-built (CGW #212)."
Bottom note: "The gateway owns scheduling — PD-1 passes scheduled_time and does NOT buffer
the send itself (no SQS)." Clean, minimal, flat cloud/phone icons.

Prompt 8 — Worked example: Weekly Forecast, Monday 08:00 (sequence)

Section titled “Prompt 8 — Worked example: Weekly Forecast, Monday 08:00 (sequence)”
A vertical sequence/timeline diagram titled "Worked Example — Weekly Forecast, Monday 08:00".
Actors as labelled lanes left-to-right: "Cron loop", "Registry (PD-3)", "Neo4j + experiment
store", "Pipeline (stages 2–6)", "Dispatch (stage 7)", "notification-gateway (Growth)",
"DynamoDB audit", "Snowflake/PLT-629". Ordered steps as numbered arrows down the timeline:
1) Cron tick at 08:00 (evaluated per user timezone) → DueChecker.Due = true.
2) Mint dm_run_id (uuid4); one replica wins the (weekly-forecast, 2026-…T08:00) claim via
conditional DDB write (RunClaimer) — other replicas skip.
3) Candidate query runs ONCE (the kind:callable bound at startup): experiment audience →
strict shelf query → cold-start fallback → stitch → N users, one candidate row each.
4) Fan-out: N FireEvents (fresh session_id each) into a 32-worker pool.
5) Per fire, stages 2–6: CCS enrich → eligibility (min_shelf_items:3 gate) → (no ranking)
→ Jinja copy render → cohort-flag re-check → resolve Eppo arm.
6) Stage 7 dispatch (inline): render idempotency_key = userId:weekly:2026-06-15 → per-type
cap reserve → translate "MON 08:00 tz:user" to the user's wall-clock instant →
POST /v1/gateway/notify with scheduled_time set.
7) Gateway returns 202; holds the send in its Valkey scheduler until 08:00 local; at release
runs its 12 guardrails and dispatches via APNs/FCM → push; durable episode renders in-app.
8) Audit: one ScheduleEvent row per user written SYNC before the fire returns — dispatched
(notification_id + scheduled_at) or a skipped_*/failed outcome.
9) DDB Streams → Snowflake; PLT-629 funnel reads the merged (dm_run_id, user_id) row.
Callout: "One cron tick → one dm_run_id → N fires → N audit rows. The 08:00 fire schedules an
08:00-local delivery; the gateway owns the deferral, not PD-1." Blue/green accent, clean,
monospace for ids and keys.

  • Generate each prompt as its own image — do not merge back into one canvas (the point of the split is clarity). The set is 1 overview (Prompt 1) + 7 details (Prompts 2–8).
  • The colour legend across all images: GREEN = PD-3, BLUE = PD-1, PURPLE = PD-2 (now inline), GREY = Growth-owned / external, AMBER = shared/infra.
  • The status-chip legend: GREEN/“LIVE” = merged on main (PD-3 + PD-1 core + CCS); BLUE/ “REVIEW-READY (PR #NNN)” = in the open stack #228–#237; “PLANNED” = not built (Kafka mode, one-off-fire route). Cite the PR number on review-ready chips.
  • The three most common errors to avoid: (1) drawing an SQS queue / separate PD-2 consumer (removed by ADR-0001); (2) inventing audit PK/SK or status values — use the pinned facts at the top of this file; (3) marking the adapter pieces “LIVE” — they are review-ready, not yet merged (see the status legend at the top).