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), SKuser_id, dedupe keyevent_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-2skipped_weekly_cap/skipped_quota/dropped_no_devices). - Gateway endpoint:
POST /v1/gateway/notify. Stage 2 = CCS enrichment (batched). - Dispatch row fields:
notification_id(wassqs_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 firingmodes 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 capall 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 wasremoved; 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 witharrows into a single wide box "ONE FireEvent per (user, instant)" listing fields:DMRunID (uuid4), SessionID (uuid4), UserID (from candidate row or trigger payload — NEVERfrom 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 (runcandidate_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 (structuredlogs + 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'scandidate_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 withWithCallableResolution() 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 → SnowflakeDM_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 Mon00: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 thesystem 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-switchand 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 boxlisting 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 notre-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/greyaccent. 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 bufferthe 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 + experimentstore", "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 an08:00-local delivery; the gateway owns the deferral, not PD-1." Blue/green accent, clean,monospace for ids and keys.Notes for whoever regenerates
Section titled “Notes for whoever regenerates”- 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).