skilldrop-cli 0.16.22 → 0.16.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/package.json +1 -1
  2. package/packs/solution-architect/skills/interactive-diagram/SKILL.md +37 -9
  3. package/packs/solution-architect/skills/interactive-diagram/evals/evals.json +90 -0
  4. package/packs/solution-architect/skills/interactive-diagram/evals/files/10/build-4127-timing.json +179 -0
  5. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/AbstractSensor.java +36 -0
  6. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/AlertService.java +15 -0
  7. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/Greenhouse.java +36 -0
  8. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/Sensor.java +10 -0
  9. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/SoilMoistureSensor.java +25 -0
  10. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/TemperatureSensor.java +19 -0
  11. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/Zone.java +26 -0
  12. package/packs/solution-architect/skills/interactive-diagram/evals/files/6/notes.txt +7 -0
  13. package/packs/solution-architect/skills/interactive-diagram/evals/files/6/trace-quote-slow.jsonl +13 -0
  14. package/packs/solution-architect/skills/interactive-diagram/evals/files/7/oncall-notes.md +16 -0
  15. package/packs/solution-architect/skills/interactive-diagram/evals/files/7/system-log.txt +14 -0
  16. package/packs/solution-architect/skills/interactive-diagram/evals/files/8/run-4471.json +151 -0
  17. package/packs/solution-architect/skills/interactive-diagram/evals/files/9/chat-export.txt +16 -0
  18. package/packs/solution-architect/skills/interactive-diagram/evals/files/9/lesson-sync.log +24 -0
  19. package/packs/solution-architect/skills/interactive-diagram/evals/files/9/ticket-8812.csv +6 -0
  20. package/packs/solution-architect/skills/interactive-diagram/examples/checkout-map.html +25 -8
  21. package/packs/solution-architect/skills/interactive-diagram/examples/checkout-trace.html +203 -0
  22. package/packs/solution-architect/skills/interactive-diagram/examples/checkout-trace.json +110 -0
  23. package/packs/solution-architect/skills/interactive-diagram/examples/incident-timeline.html +203 -0
  24. package/packs/solution-architect/skills/interactive-diagram/examples/incident-timeline.json +21 -0
  25. package/packs/solution-architect/skills/interactive-diagram/examples/inputs/incident/oncall-notes.md +14 -0
  26. package/packs/solution-architect/skills/interactive-diagram/examples/inputs/trace/checkout-trace.jsonl +9 -0
  27. package/packs/solution-architect/skills/interactive-diagram/examples/leave-approval.html +25 -8
  28. package/packs/solution-architect/skills/interactive-diagram/manifest.json +2 -2
  29. package/packs/solution-architect/skills/interactive-diagram/reference.md +86 -6
  30. package/packs/solution-architect/skills/interactive-diagram/scripts/build_diagram.py +1025 -102
@@ -0,0 +1,151 @@
1
+ {
2
+ "export": "orchestrator run export v2",
3
+ "pipeline": "nightly-sky-ingest",
4
+ "run_id": "run-4471",
5
+ "observatory": "Example Ridge Observatory",
6
+ "timezone": "UTC",
7
+ "run_status": "succeeded",
8
+ "run_reported_finished_at": "2026-10-17T01:25:00Z",
9
+ "steps": [
10
+ {
11
+ "id": "s01",
12
+ "name": "nightly-sky-ingest",
13
+ "parent_id": null,
14
+ "started_at": "2026-10-17T01:00:00Z",
15
+ "finished_at": "2026-10-17T01:25:00Z",
16
+ "status": "succeeded",
17
+ "host": "orchestrator"
18
+ },
19
+ {
20
+ "id": "s02",
21
+ "name": "checkout_config",
22
+ "parent_id": "s01",
23
+ "started_at": "2026-10-17T01:00:00Z",
24
+ "finished_at": "2026-10-17T01:00:20Z",
25
+ "status": "succeeded",
26
+ "host": "runner-2"
27
+ },
28
+ {
29
+ "id": "s03",
30
+ "name": "fetch_frames",
31
+ "parent_id": "s01",
32
+ "started_at": "2026-10-17T01:00:20Z",
33
+ "finished_at": "2026-10-17T01:10:20Z",
34
+ "status": "succeeded",
35
+ "host": "runner-2"
36
+ },
37
+ {
38
+ "id": "s04",
39
+ "name": "fetch_north",
40
+ "parent_id": "s03",
41
+ "started_at": "2026-10-17T01:00:22Z",
42
+ "finished_at": "2026-10-17T01:05:00Z",
43
+ "status": "succeeded",
44
+ "host": "runner-3",
45
+ "site": "north-dome"
46
+ },
47
+ {
48
+ "id": "s05",
49
+ "name": "fetch_south",
50
+ "parent_id": "s03",
51
+ "started_at": "2026-10-17T01:00:22Z",
52
+ "finished_at": "2026-10-17T01:10:10Z",
53
+ "status": "succeeded",
54
+ "host": "runner-4",
55
+ "site": "south-dome"
56
+ },
57
+ {
58
+ "id": "s06",
59
+ "name": "fetch_east",
60
+ "parent_id": "s03",
61
+ "started_at": "2026-10-17T01:00:22Z",
62
+ "finished_at": "2026-10-17T01:04:00Z",
63
+ "status": "succeeded",
64
+ "host": "runner-5",
65
+ "site": "east-dome"
66
+ },
67
+ {
68
+ "id": "s07",
69
+ "name": "verify_checksums",
70
+ "parent_id": "s03",
71
+ "started_at": "2026-10-17T01:10:10Z",
72
+ "finished_at": "2026-10-17T01:10:18Z",
73
+ "status": "succeeded",
74
+ "host": "runner-2"
75
+ },
76
+ {
77
+ "id": "s08",
78
+ "name": "calibrate",
79
+ "parent_id": "s01",
80
+ "started_at": "2026-10-17T01:10:20Z",
81
+ "finished_at": "2026-10-17T01:18:20Z",
82
+ "status": "succeeded",
83
+ "host": "gpu-1"
84
+ },
85
+ {
86
+ "id": "s09",
87
+ "name": "load_darks",
88
+ "parent_id": "s08",
89
+ "started_at": "2026-10-17T01:10:20Z",
90
+ "finished_at": "2026-10-17T01:10:50Z",
91
+ "status": "succeeded",
92
+ "host": "gpu-1"
93
+ },
94
+ {
95
+ "id": "s10",
96
+ "name": "load_flats",
97
+ "parent_id": "s08",
98
+ "started_at": "2026-10-17T01:10:50Z",
99
+ "finished_at": "2026-10-17T01:11:20Z",
100
+ "status": "succeeded",
101
+ "host": "gpu-1"
102
+ },
103
+ {
104
+ "id": "s11",
105
+ "name": "stack_and_publish",
106
+ "parent_id": "s01",
107
+ "started_at": "2026-10-17T01:18:20Z",
108
+ "finished_at": "2026-10-17T01:24:52Z",
109
+ "status": "succeeded",
110
+ "host": "runner-2"
111
+ },
112
+ {
113
+ "id": "s12",
114
+ "name": "stack_mosaic",
115
+ "parent_id": "s11",
116
+ "started_at": "2026-10-17T01:18:20Z",
117
+ "finished_at": "2026-10-17T01:23:00Z",
118
+ "status": "succeeded",
119
+ "host": "gpu-1"
120
+ },
121
+ {
122
+ "id": "s13",
123
+ "name": "publish_catalog",
124
+ "parent_id": "s11",
125
+ "started_at": "2026-10-17T01:23:00Z",
126
+ "finished_at": "2026-10-17T01:24:50Z",
127
+ "status": "succeeded",
128
+ "host": "runner-2",
129
+ "target": "catalog-db"
130
+ },
131
+ {
132
+ "id": "s14",
133
+ "name": "upload_thumbnails",
134
+ "parent_id": "s11",
135
+ "started_at": "2026-10-17T01:23:05Z",
136
+ "finished_at": "2026-10-17T01:28:40Z",
137
+ "status": "succeeded",
138
+ "host": "runner-6",
139
+ "detached": true
140
+ },
141
+ {
142
+ "id": "s15",
143
+ "name": "send_summary",
144
+ "parent_id": "s01",
145
+ "started_at": "2026-10-17T01:24:52Z",
146
+ "finished_at": "2026-10-17T01:25:00Z",
147
+ "status": "succeeded",
148
+ "host": "runner-2"
149
+ }
150
+ ]
151
+ }
@@ -0,0 +1,16 @@
1
+ Chat export: #lesson-sync-oncall
2
+ Exported by: support-mei
3
+ Times shown in Asia/Kolkata (UTC+05:30)
4
+
5
+ [15/10/2026, 03:08] deploy-bot: lesson-sync v4.12.0 rolling out to prod (started by @dev-ravi)
6
+ [15/10/2026, 03:10] deploy-bot: lesson-sync v4.12.0 healthy on 6/6 pods
7
+ [15/10/2026, 03:31] support-mei: schools in the north region say lesson downloads are stuck at 0% in the app. opening ticket 8812
8
+ [15/10/2026, 03:36] oncall-ana: looking. seeing "manifest checksum mismatch" in lesson-sync since about 03:22
9
+ [15/10/2026, 03:44] oncall-ana: only on the v4.12.0 pods as far as I can tell
10
+ [15/10/2026, 03:55] oncall-ana: rolling back to v4.11.3
11
+ [15/10/2026, 03:58] dev-ravi: did anyone purge the CDN? I *think* a purge ran around 03:50 but I can't find who or when, don't quote me
12
+ [15/10/2026, 04:01] deploy-bot: lesson-sync v4.11.3 healthy on 6/6 pods
13
+ [15/10/2026, 04:06] oncall-ana: errors stopped at 04:03. watching
14
+ [15/10/2026, 04:20] oncall-ana: support says the school confirmed downloads work again. going to bed, will look at why 4.12 broke checksums in the morning
15
+ [15/10/2026, 11:42] dev-ravi: bumping max_retries 3 -> 5 on the lesson-sync config, been meaning to do that for weeks anyway
16
+ [15/10/2026, 12:10] support-mei: closing 8812
@@ -0,0 +1,24 @@
1
+ # lesson-sync production log, all pods merged
2
+ # filter: level>=INFO, health checks and per-request lines excluded
3
+ # timestamps UTC
4
+ 2026-10-14T21:30:00Z INFO metrics download_success_rate_5m=99.7% pods=6 version=4.11.3
5
+ 2026-10-14T21:38:02Z INFO deploy started version=4.12.0 pods=6 by=dev-ravi
6
+ 2026-10-14T21:40:12Z INFO deploy complete version=4.12.0 healthy=6/6
7
+ 2026-10-14T21:45:00Z INFO metrics download_success_rate_5m=99.5% pods=6 version=4.12.0
8
+ 2026-10-14T21:52:30Z ERROR manifest checksum mismatch pack=grade7-science-u3 pod=lesson-sync-4120-a
9
+ 2026-10-14T21:53:11Z ERROR manifest checksum mismatch pack=grade5-maths-u1 pod=lesson-sync-4120-c
10
+ 2026-10-14T21:55:47Z ERROR manifest checksum mismatch pack=grade7-science-u3 pod=lesson-sync-4120-b
11
+ 2026-10-14T22:00:00Z INFO metrics download_success_rate_5m=41.2% pods=6 version=4.12.0
12
+ 2026-10-14T22:03:19Z ERROR manifest checksum mismatch pack=grade6-history-u2 pod=lesson-sync-4120-e
13
+ 2026-10-14T22:09:52Z ERROR manifest checksum mismatch pack=grade5-maths-u1 pod=lesson-sync-4120-a
14
+ 2026-10-14T22:15:00Z INFO metrics download_success_rate_5m=38.9% pods=6 version=4.12.0
15
+ 2026-10-14T22:17:28Z ERROR manifest checksum mismatch pack=grade8-english-u4 pod=lesson-sync-4120-d
16
+ 2026-10-14T22:24:05Z ERROR manifest checksum mismatch pack=grade7-science-u3 pod=lesson-sync-4120-f
17
+ 2026-10-14T22:25:40Z INFO rollback started version=4.11.3 by=oncall-ana
18
+ 2026-10-14T22:29:13Z ERROR manifest checksum mismatch pack=grade6-history-u2 pod=lesson-sync-4120-b
19
+ 2026-10-14T22:31:04Z INFO rollback complete version=4.11.3 healthy=6/6
20
+ 2026-10-14T22:33:41Z ERROR manifest checksum mismatch pack=grade5-maths-u1 pod=lesson-sync-4113-c
21
+ 2026-10-14T22:40:00Z INFO metrics download_success_rate_5m=99.6% pods=6 version=4.11.3
22
+ 2026-10-15T06:12:00Z INFO config reload requested max_retries=5 (was 3) by=dev-ravi
23
+ 2026-10-15T06:12:03Z INFO config reload complete healthy=6/6
24
+ 2026-10-15T06:15:00Z INFO metrics download_success_rate_5m=99.7% pods=6 version=4.11.3
@@ -0,0 +1,6 @@
1
+ timestamp_utc,actor,action,field,old_value,new_value,comment
2
+ 2026-10-14T22:05:00Z,support-mei,created,status,,Open,"Several schools in the north region: lesson downloads stuck at 0% in the app"
3
+ 2026-10-14T22:05:00Z,support-mei,set,priority,,P2,
4
+ 2026-10-14T22:12:00Z,oncall-ana,set,priority,P2,P1,"Errors in lesson-sync, investigating"
5
+ 2026-10-14T22:48:00Z,support-mei,comment,,,,"School contact confirms downloads are completing again"
6
+ 2026-10-15T06:40:00Z,support-mei,set,status,Open,Resolved,"Fixed by the retry config change (max_retries 3 -> 5) deployed this morning"
@@ -1,6 +1,6 @@
1
1
  <!doctype html>
2
2
  <html lang="en" data-theme-default="auto"><head><meta charset="utf-8">
3
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-x8uMUP6tcCdtva9qPkw2RN4FPyZ3BQ4hbm/v2UzDBzA='; style-src 'unsafe-inline'; img-src data: blob:; base-uri 'none'; form-action 'none'">
3
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-KdTstAM29gVIP104X7Tju5/PTeQKbRDMMG1TjJdsOyI='; style-src 'unsafe-inline'; img-src data: blob:; base-uri 'none'; form-action 'none'">
4
4
  <meta name="viewport" content="width=device-width,initial-scale=1">
5
5
  <!-- One self-contained file: no external scripts, styles, fonts or requests. The policy above makes the browser refuse any. -->
6
6
  <title>What a checkout request touches</title><meta name="generator" content="interactive-diagram (skilldrop)"></head>
@@ -17,7 +17,7 @@
17
17
  <span class="hint">Click a part for detail and evidence · shift-click a second part for the path · drag to pan · scroll to zoom</span></nav>
18
18
  <div class="legend"><span><i></i>connection</span><span><i class="d"></i>async / optional</span><span><i class="a"></i>assumption (not shown by the evidence)</span></div>
19
19
  <div class="caption" id="caption" aria-live="polite"></div>
20
- <div class="wrap"><div id="stage"><svg id="map" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1177 406" width="1177" height="406" role="group" aria-label="What a checkout request touches"><defs><marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" class="arrowhead"/></marker><marker id="arrow-start" viewBox="0 0 10 10" refX="1" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" class="arrowhead"/></marker></defs><rect class="canvas" x="0" y="0" width="1177" height="406"/><g class="group d1" data-group="compose"><rect x="223.0" y="114.0" width="914.0" height="252.0" rx="10"/><text class="glabel" x="235.0" y="132.0">docker-compose stack</text></g><g class="group d2" data-group="data"><rect x="506.0" y="230.0" width="613.0" height="118.0" rx="10"/><text class="glabel" x="518.0" y="248.0">Data</text></g><g class="edge s-solid assumed emph" id="e-pay" data-id="pay" data-from="shopper" data-to="api"><title>POST /checkout</title><path class="hit" d="M187.0,57.0 L216.0,57.0 Q223.0,57.0 223.0,64.0 L223.0,166.0 Q223.0,173.0 230.0,173.0 L259.0,173.0"/><path class="line" d="M187.0,57.0 L216.0,57.0 Q223.0,57.0 223.0,64.0 L223.0,166.0 Q223.0,173.0 230.0,173.0 L259.0,173.0" marker-end="url(#arrow)"/><rect class="lbg" x="173.7" y="82.8" width="98.7" height="18.0" rx="3"/><text class="elabel" x="223.0" y="95.8" text-anchor="middle">POST /checkout</text></g><g class="edge s-solid" id="e-price" data-id="price" data-from="api" data-to="cache"><title>get price:&lt;sku&gt;</title><path class="hit" d="M446.0,178.0 L479.5,178.0 Q486.5,178.0 486.5,185.0 L486.5,289.0 Q486.5,296.0 493.5,296.0 L524.0,296.0"/><path class="line" d="M446.0,178.0 L479.5,178.0 Q486.5,178.0 486.5,185.0 L486.5,289.0 Q486.5,296.0 493.5,296.0 L524.0,296.0" marker-end="url(#arrow)"/><rect class="lbg" x="438.9" y="204.4" width="95.2" height="18.0" rx="3"/><text class="elabel" x="486.5" y="217.4" text-anchor="middle">get price:&lt;sku&gt;</text></g><g class="edge s-solid" id="e-miss" data-id="miss" data-from="api" data-to="db"><title>price on cache miss, insert order</title><path class="hit" d="M446.0,188.1 L470.5,188.1 Q477.5,188.1 477.5,195.1 L477.5,208.0 Q477.5,215.0 484.5,215.0 L920.5,215.0 Q927.5,215.0 927.5,222.0 L927.5,270.0 Q927.5,277.0 934.5,277.0 L959.0,277.0"/><path class="line" d="M446.0,188.1 L470.5,188.1 Q477.5,188.1 477.5,195.1 L477.5,208.0 Q477.5,215.0 484.5,215.0 L920.5,215.0 Q927.5,215.0 927.5,222.0 L927.5,270.0 Q927.5,277.0 934.5,277.0 L959.0,277.0" marker-end="url(#arrow)"/><rect class="lbg" x="729.4" y="199.0" width="126.2" height="32.0" rx="3"/><text class="elabel" x="792.5" y="212.0" text-anchor="middle">price on cache miss,</text><text class="elabel" x="792.5" y="226.0" text-anchor="middle">insert order</text></g><g class="edge s-solid emph" id="e-charge" data-id="charge" data-from="api" data-to="stripe"><title>create + confirm payment</title><path class="hit" d="M446.0,157.9 L470.5,157.9 Q477.5,157.9 477.5,150.9 L477.5,64.0 Q477.5,57.0 484.5,57.0 L529.0,57.0"/><path class="line" d="M446.0,157.9 L470.5,157.9 Q477.5,157.9 477.5,150.9 L477.5,64.0 Q477.5,57.0 484.5,57.0 L529.0,57.0" marker-end="url(#arrow)"/><rect class="lbg" x="397.9" y="98.4" width="159.3" height="18.0" rx="3"/><text class="elabel" x="477.5" y="111.4" text-anchor="middle">create + confirm payment</text></g><g class="edge s-dashed" id="e-publish" data-id="publish" data-from="api" data-to="broker"><title>publish order_id</title><path class="hit" d="M446.0,168.0 L475.0,168.0 Q477.5,168.0 477.5,170.5 L477.5,170.5 Q477.5,173.0 480.0,173.0 L518.0,173.0"/><path class="line" d="M446.0,168.0 L475.0,168.0 Q477.5,168.0 477.5,170.5 L477.5,170.5 Q477.5,173.0 480.0,173.0 L518.0,173.0" marker-end="url(#arrow)"/><rect class="lbg" x="448.5" y="164.0" width="98.5" height="18.0" rx="3"/><text class="elabel" x="497.8" y="177.0" text-anchor="middle">publish order_id</text></g><g class="edge s-dashed" id="e-consume" data-id="consume" data-from="broker" data-to="worker"><title>consume</title><path class="hit" d="M660.0,173.0 L689.0,173.0 Q696.0,173.0 703.0,173.0 L732.0,173.0"/><path class="line" d="M660.0,173.0 L689.0,173.0 Q696.0,173.0 703.0,173.0 L732.0,173.0" marker-end="url(#arrow)"/><rect class="lbg" x="662.7" y="164.0" width="55.8" height="18.0" rx="3"/><text class="elabel" x="690.6" y="177.0" text-anchor="middle">consume</text></g><g class="edge s-solid" id="e-book" data-id="book" data-from="worker" data-to="courier"><title>book courier</title><path class="hit" d="M887.0,159.0 L911.5,159.0 Q918.5,159.0 918.5,152.0 L918.5,64.0 Q918.5,57.0 925.5,57.0 L970.0,57.0"/><path class="line" d="M887.0,159.0 L911.5,159.0 Q918.5,159.0 918.5,152.0 L918.5,64.0 Q918.5,57.0 925.5,57.0 L970.0,57.0" marker-end="url(#arrow)"/><rect class="lbg" x="878.6" y="99.0" width="79.8" height="18.0" rx="3"/><text class="elabel" x="918.5" y="112.0" text-anchor="middle">book courier</text></g><g class="edge s-solid" id="e-mark" data-id="mark" data-from="worker" data-to="db"><title>read address, mark booked</title><path class="hit" d="M887.0,187.0 L911.5,187.0 Q918.5,187.0 918.5,194.0 L918.5,308.0 Q918.5,315.0 925.5,315.0 L959.0,315.0"/><path class="line" d="M887.0,187.0 L911.5,187.0 Q918.5,187.0 918.5,194.0 L918.5,308.0 Q918.5,315.0 925.5,315.0 L959.0,315.0" marker-end="url(#arrow)"/><rect class="lbg" x="858.7" y="209.4" width="119.6" height="32.0" rx="3"/><text class="elabel" x="918.5" y="222.4" text-anchor="middle">read address, mark</text><text class="elabel" x="918.5" y="236.4" text-anchor="middle">booked</text></g><g class="node c-violet k-client assumed" id="n-shopper" data-id="shopper" tabindex="0" role="button" aria-label="Shopper&#x27;s browser (assumption)"><title>Shopper&#x27;s browser</title><rect class="shape" x="30.0" y="32.0" width="157.0" height="50.0" rx="6"/><text class="label" x="108.5" y="61.5" text-anchor="middle">Shopper&#x27;s browser</text><text class="badge warn" x="182.0" y="28.0" text-anchor="end">assumed</text></g><g class="node c-blue k-service" id="n-api" data-id="api" tabindex="0" role="button" aria-label="Checkout API"><title>Checkout API — FastAPI · POST /checkout</title><rect class="shape" x="259.0" y="146.0" width="187.0" height="54.0" rx="6"/><text class="label" x="352.5" y="170.0" text-anchor="middle">Checkout API</text><text class="note" x="352.5" y="186.0" text-anchor="middle">FastAPI · POST /checkout</text><text class="badge" x="441.0" y="142.0" text-anchor="end">✓ src</text></g><g class="node c-teal k-cache" id="n-cache" data-id="cache" tabindex="0" role="button" aria-label="Price cache"><title>Price cache — Redis, 5 min TTL</title><path class="shape" d="M524.0,269.0 a65.0,7 0 0,1 130.0,0 v54.0 a65.0,7 0 0,1 -130.0,0 z"/><path class="shape-line" d="M524.0,269.0 a65.0,7 0 0,0 130.0,0"/><text class="label" x="589.0" y="297.0" text-anchor="middle">Price cache</text><text class="note" x="589.0" y="313.0" text-anchor="middle">Redis, 5 min TTL</text><text class="badge" x="649.0" y="258.0" text-anchor="end">✓ src</text></g><g class="node c-green k-database" id="n-db" data-id="db" tabindex="0" role="button" aria-label="Orders database"><title>Orders database — Postgres 16</title><path class="shape" d="M959.0,269.0 a71.0,7 0 0,1 142.0,0 v54.0 a71.0,7 0 0,1 -142.0,0 z"/><path class="shape-line" d="M959.0,269.0 a71.0,7 0 0,0 142.0,0"/><text class="label" x="1030.0" y="297.0" text-anchor="middle">Orders database</text><text class="note" x="1030.0" y="313.0" text-anchor="middle">Postgres 16</text><text class="badge" x="1096.0" y="258.0" text-anchor="end">✓ src</text></g><g class="node c-amber k-queue" id="n-broker" data-id="broker" tabindex="0" role="button" aria-label="fulfilment queue"><title>fulfilment queue — RabbitMQ</title><rect class="shape" x="518.0" y="146.0" width="142.0" height="54.0" rx="10"/><path class="shape-line" d="M528.0,149.0 v48.0 M650.0,149.0 v48.0"/><text class="label" x="589.0" y="170.0" text-anchor="middle">fulfilment queue</text><text class="note" x="589.0" y="186.0" text-anchor="middle">RabbitMQ</text><text class="badge" x="655.0" y="142.0" text-anchor="end">✓ src</text></g><g class="node c-indigo k-worker" id="n-worker" data-id="worker" tabindex="0" role="button" aria-label="Fulfilment worker"><title>Fulfilment worker</title><rect class="shape" x="732.0" y="148.0" width="155.0" height="50.0" rx="6"/><text class="label" x="809.5" y="177.5" text-anchor="middle">Fulfilment worker</text><text class="badge" x="882.0" y="144.0" text-anchor="end">✓ src</text></g><g class="node c-slate k-external" id="n-stripe" data-id="stripe" tabindex="0" role="button" aria-label="Stripe"><title>Stripe — PaymentIntent</title><rect class="shape" x="529.0" y="30.0" width="120.0" height="54.0" rx="4"/><text class="label" x="589.0" y="54.0" text-anchor="middle">Stripe</text><text class="note" x="589.0" y="70.0" text-anchor="middle">PaymentIntent</text><text class="badge" x="644.0" y="26.0" text-anchor="end">✓ src</text></g><g class="node c-slate k-external" id="n-courier" data-id="courier" tabindex="0" role="button" aria-label="Courier API"><title>Courier API — courier.example</title><rect class="shape" x="970.0" y="30.0" width="120.0" height="54.0" rx="4"/><text class="label" x="1030.0" y="54.0" text-anchor="middle">Courier API</text><text class="note" x="1030.0" y="70.0" text-anchor="middle">courier.example</text><text class="badge" x="1085.0" y="26.0" text-anchor="end">✓ src</text></g></svg></div><aside id="panel" aria-live="polite"></aside></div>
20
+ <div class="wrap"><div id="stage"><svg id="map" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1177 406" width="1177" height="406" role="group" aria-label="What a checkout request touches"><defs><marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="10" markerHeight="10" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" class="arrowhead"/></marker><marker id="arrow-start" viewBox="0 0 10 10" refX="1" refY="5" markerUnits="userSpaceOnUse" markerWidth="10" markerHeight="10" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" class="arrowhead"/></marker><marker id="tri" viewBox="0 0 12 12" refX="11" refY="6" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto-start-reverse"><path d="M1,1 L11,6 L1,11 z" class="mk-open"/></marker><marker id="open" viewBox="0 0 12 12" refX="11" refY="6" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto-start-reverse"><path d="M1,1 L11,6 L1,11" class="mk-line"/></marker><marker id="dia-solid" viewBox="0 0 16 10" refX="15" refY="5" markerUnits="userSpaceOnUse" markerWidth="18" markerHeight="11" orient="auto-start-reverse"><path d="M1,5 L8,1 L15,5 L8,9 z" class="arrowhead"/></marker><marker id="dia-open" viewBox="0 0 16 10" refX="15" refY="5" markerUnits="userSpaceOnUse" markerWidth="18" markerHeight="11" orient="auto-start-reverse"><path d="M1,5 L8,1 L15,5 L8,9 z" class="mk-open"/></marker></defs><rect class="canvas" x="0" y="0" width="1177" height="406"/><g class="group d1" data-group="compose"><rect x="223.0" y="114.0" width="914.0" height="252.0" rx="10"/><text class="glabel" x="235.0" y="132.0">docker-compose stack</text></g><g class="group d2" data-group="data"><rect x="506.0" y="230.0" width="613.0" height="118.0" rx="10"/><text class="glabel" x="518.0" y="248.0">Data</text></g><g class="edge s-solid assumed emph" id="e-pay" data-id="pay" data-from="shopper" data-to="api"><title>POST /checkout</title><path class="hit" d="M187.0,57.0 L216.0,57.0 Q223.0,57.0 223.0,64.0 L223.0,166.0 Q223.0,173.0 230.0,173.0 L259.0,173.0"/><path class="line" d="M187.0,57.0 L216.0,57.0 Q223.0,57.0 223.0,64.0 L223.0,166.0 Q223.0,173.0 230.0,173.0 L259.0,173.0" marker-end="url(#arrow)"/><rect class="lbg" x="173.7" y="82.8" width="98.7" height="18.0" rx="3"/><text class="elabel" x="223.0" y="95.8" text-anchor="middle">POST /checkout</text></g><g class="edge s-solid" id="e-price" data-id="price" data-from="api" data-to="cache"><title>get price:&lt;sku&gt;</title><path class="hit" d="M446.0,178.0 L479.5,178.0 Q486.5,178.0 486.5,185.0 L486.5,289.0 Q486.5,296.0 493.5,296.0 L524.0,296.0"/><path class="line" d="M446.0,178.0 L479.5,178.0 Q486.5,178.0 486.5,185.0 L486.5,289.0 Q486.5,296.0 493.5,296.0 L524.0,296.0" marker-end="url(#arrow)"/><rect class="lbg" x="438.9" y="204.4" width="95.2" height="18.0" rx="3"/><text class="elabel" x="486.5" y="217.4" text-anchor="middle">get price:&lt;sku&gt;</text></g><g class="edge s-solid" id="e-miss" data-id="miss" data-from="api" data-to="db"><title>price on cache miss, insert order</title><path class="hit" d="M446.0,188.1 L470.5,188.1 Q477.5,188.1 477.5,195.1 L477.5,208.0 Q477.5,215.0 484.5,215.0 L920.5,215.0 Q927.5,215.0 927.5,222.0 L927.5,270.0 Q927.5,277.0 934.5,277.0 L959.0,277.0"/><path class="line" d="M446.0,188.1 L470.5,188.1 Q477.5,188.1 477.5,195.1 L477.5,208.0 Q477.5,215.0 484.5,215.0 L920.5,215.0 Q927.5,215.0 927.5,222.0 L927.5,270.0 Q927.5,277.0 934.5,277.0 L959.0,277.0" marker-end="url(#arrow)"/><rect class="lbg" x="729.4" y="199.0" width="126.2" height="32.0" rx="3"/><text class="elabel" x="792.5" y="212.0" text-anchor="middle">price on cache miss,</text><text class="elabel" x="792.5" y="226.0" text-anchor="middle">insert order</text></g><g class="edge s-solid emph" id="e-charge" data-id="charge" data-from="api" data-to="stripe"><title>create + confirm payment</title><path class="hit" d="M446.0,157.9 L470.5,157.9 Q477.5,157.9 477.5,150.9 L477.5,64.0 Q477.5,57.0 484.5,57.0 L529.0,57.0"/><path class="line" d="M446.0,157.9 L470.5,157.9 Q477.5,157.9 477.5,150.9 L477.5,64.0 Q477.5,57.0 484.5,57.0 L529.0,57.0" marker-end="url(#arrow)"/><rect class="lbg" x="397.9" y="98.4" width="159.3" height="18.0" rx="3"/><text class="elabel" x="477.5" y="111.4" text-anchor="middle">create + confirm payment</text></g><g class="edge s-dashed" id="e-publish" data-id="publish" data-from="api" data-to="broker"><title>publish order_id</title><path class="hit" d="M446.0,168.0 L475.0,168.0 Q477.5,168.0 477.5,170.5 L477.5,170.5 Q477.5,173.0 480.0,173.0 L518.0,173.0"/><path class="line" d="M446.0,168.0 L475.0,168.0 Q477.5,168.0 477.5,170.5 L477.5,170.5 Q477.5,173.0 480.0,173.0 L518.0,173.0" marker-end="url(#arrow)"/><rect class="lbg" x="448.5" y="164.0" width="98.5" height="18.0" rx="3"/><text class="elabel" x="497.8" y="177.0" text-anchor="middle">publish order_id</text></g><g class="edge s-dashed" id="e-consume" data-id="consume" data-from="broker" data-to="worker"><title>consume</title><path class="hit" d="M660.0,173.0 L689.0,173.0 Q696.0,173.0 703.0,173.0 L732.0,173.0"/><path class="line" d="M660.0,173.0 L689.0,173.0 Q696.0,173.0 703.0,173.0 L732.0,173.0" marker-end="url(#arrow)"/><rect class="lbg" x="662.7" y="164.0" width="55.8" height="18.0" rx="3"/><text class="elabel" x="690.6" y="177.0" text-anchor="middle">consume</text></g><g class="edge s-solid" id="e-book" data-id="book" data-from="worker" data-to="courier"><title>book courier</title><path class="hit" d="M887.0,159.0 L911.5,159.0 Q918.5,159.0 918.5,152.0 L918.5,64.0 Q918.5,57.0 925.5,57.0 L970.0,57.0"/><path class="line" d="M887.0,159.0 L911.5,159.0 Q918.5,159.0 918.5,152.0 L918.5,64.0 Q918.5,57.0 925.5,57.0 L970.0,57.0" marker-end="url(#arrow)"/><rect class="lbg" x="878.6" y="99.0" width="79.8" height="18.0" rx="3"/><text class="elabel" x="918.5" y="112.0" text-anchor="middle">book courier</text></g><g class="edge s-solid" id="e-mark" data-id="mark" data-from="worker" data-to="db"><title>read address, mark booked</title><path class="hit" d="M887.0,187.0 L911.5,187.0 Q918.5,187.0 918.5,194.0 L918.5,308.0 Q918.5,315.0 925.5,315.0 L959.0,315.0"/><path class="line" d="M887.0,187.0 L911.5,187.0 Q918.5,187.0 918.5,194.0 L918.5,308.0 Q918.5,315.0 925.5,315.0 L959.0,315.0" marker-end="url(#arrow)"/><rect class="lbg" x="858.7" y="209.4" width="119.6" height="32.0" rx="3"/><text class="elabel" x="918.5" y="222.4" text-anchor="middle">read address, mark</text><text class="elabel" x="918.5" y="236.4" text-anchor="middle">booked</text></g><g class="node c-violet k-client assumed" id="n-shopper" data-id="shopper" tabindex="0" role="button" aria-label="Shopper&#x27;s browser (assumption)"><title>Shopper&#x27;s browser</title><rect class="shape" x="30.0" y="32.0" width="157.0" height="50.0" rx="6"/><text class="label" x="108.5" y="61.5" text-anchor="middle">Shopper&#x27;s browser</text><text class="badge warn" x="182.0" y="28.0" text-anchor="end">assumed</text></g><g class="node c-blue k-service" id="n-api" data-id="api" tabindex="0" role="button" aria-label="Checkout API"><title>Checkout API — FastAPI · POST /checkout</title><rect class="shape" x="259.0" y="146.0" width="187.0" height="54.0" rx="6"/><text class="label" x="352.5" y="170.0" text-anchor="middle">Checkout API</text><text class="note" x="352.5" y="186.0" text-anchor="middle">FastAPI · POST /checkout</text><text class="badge" x="441.0" y="142.0" text-anchor="end">✓ src</text></g><g class="node c-teal k-cache" id="n-cache" data-id="cache" tabindex="0" role="button" aria-label="Price cache"><title>Price cache — Redis, 5 min TTL</title><path class="shape" d="M524.0,269.0 a65.0,7 0 0,1 130.0,0 v54.0 a65.0,7 0 0,1 -130.0,0 z"/><path class="shape-line" d="M524.0,269.0 a65.0,7 0 0,0 130.0,0"/><text class="label" x="589.0" y="297.0" text-anchor="middle">Price cache</text><text class="note" x="589.0" y="313.0" text-anchor="middle">Redis, 5 min TTL</text><text class="badge" x="649.0" y="258.0" text-anchor="end">✓ src</text></g><g class="node c-green k-database" id="n-db" data-id="db" tabindex="0" role="button" aria-label="Orders database"><title>Orders database — Postgres 16</title><path class="shape" d="M959.0,269.0 a71.0,7 0 0,1 142.0,0 v54.0 a71.0,7 0 0,1 -142.0,0 z"/><path class="shape-line" d="M959.0,269.0 a71.0,7 0 0,0 142.0,0"/><text class="label" x="1030.0" y="297.0" text-anchor="middle">Orders database</text><text class="note" x="1030.0" y="313.0" text-anchor="middle">Postgres 16</text><text class="badge" x="1096.0" y="258.0" text-anchor="end">✓ src</text></g><g class="node c-amber k-queue" id="n-broker" data-id="broker" tabindex="0" role="button" aria-label="fulfilment queue"><title>fulfilment queue — RabbitMQ</title><rect class="shape" x="518.0" y="146.0" width="142.0" height="54.0" rx="10"/><path class="shape-line" d="M528.0,149.0 v48.0 M650.0,149.0 v48.0"/><text class="label" x="589.0" y="170.0" text-anchor="middle">fulfilment queue</text><text class="note" x="589.0" y="186.0" text-anchor="middle">RabbitMQ</text><text class="badge" x="655.0" y="142.0" text-anchor="end">✓ src</text></g><g class="node c-indigo k-worker" id="n-worker" data-id="worker" tabindex="0" role="button" aria-label="Fulfilment worker"><title>Fulfilment worker</title><rect class="shape" x="732.0" y="148.0" width="155.0" height="50.0" rx="6"/><text class="label" x="809.5" y="177.5" text-anchor="middle">Fulfilment worker</text><text class="badge" x="882.0" y="144.0" text-anchor="end">✓ src</text></g><g class="node c-slate k-external" id="n-stripe" data-id="stripe" tabindex="0" role="button" aria-label="Stripe"><title>Stripe — PaymentIntent</title><rect class="shape" x="529.0" y="30.0" width="120.0" height="54.0" rx="4"/><text class="label" x="589.0" y="54.0" text-anchor="middle">Stripe</text><text class="note" x="589.0" y="70.0" text-anchor="middle">PaymentIntent</text><text class="badge" x="644.0" y="26.0" text-anchor="end">✓ src</text></g><g class="node c-slate k-external" id="n-courier" data-id="courier" tabindex="0" role="button" aria-label="Courier API"><title>Courier API — courier.example</title><rect class="shape" x="970.0" y="30.0" width="120.0" height="54.0" rx="4"/><text class="label" x="1030.0" y="54.0" text-anchor="middle">Courier API</text><text class="note" x="1030.0" y="70.0" text-anchor="middle">courier.example</text><text class="badge" x="1085.0" y="26.0" text-anchor="end">✓ src</text></g></svg></div><aside id="panel" aria-live="polite"></aside></div>
21
21
  <style id="diagram-css">
22
22
  :root{--bg:#f7f8fa;--panel:#ffffff;--ink:#16181d;--muted:#5b6170;--line:#d5d9e0;--edge:#6b7385;--accent:#2563eb;
23
23
  --warn:#b45309;--ok:#15803d;--bad:#b91c1c;--hl:#f59e0b;--canvas:#fbfcfd;--group:#eef1f6;--group2:#e6eaf2;--shadow:0 1px 2px rgba(0,0,0,.06)}
@@ -49,6 +49,11 @@ section.text{max-width:1100px;padding:10px 24px 40px}section.text h2{font-size:1
49
49
  .legend{display:flex;flex-wrap:wrap;gap:14px;font-size:12px;color:var(--muted);padding:6px 24px 0}
50
50
  .legend i{display:inline-block;width:22px;height:0;border-top:2px solid var(--edge);vertical-align:middle;margin-right:5px}
51
51
  .legend i.d{border-top-style:dashed}.legend i.a{border-top:2px dashed var(--warn)}
52
+ .findings{margin:10px 0 0;padding:8px 14px;border-left:3px solid var(--accent);background:var(--panel);border-radius:4px;max-width:1000px}
53
+ .findings ul{margin:4px 0 0;padding-left:18px}.findings li{margin:2px 0}
54
+ table.answers{border-collapse:collapse;margin:10px 0 0;max-width:1100px;font-size:13px}
55
+ table.answers th,table.answers td{border:1px solid var(--line);padding:5px 9px;text-align:left;vertical-align:top}
56
+ table.answers th{background:var(--group2);font-weight:600}table.answers td.ev{font-size:12px;color:var(--muted)}
52
57
  .issues{margin:8px 24px 0;padding:8px 12px;border:1px solid var(--warn);border-radius:6px;color:var(--warn);font-size:13px}
53
58
  /* diagram */
54
59
  .canvas{fill:var(--canvas)}
@@ -78,12 +83,23 @@ svg.focus .on,svg.focus .group.on{opacity:1}svg .node.on .shape{stroke-width:2.6
78
83
  svg .node.sel .shape{stroke:var(--hl)!important;stroke-width:3}svg .node:focus-visible .shape{stroke:var(--accent)!important;stroke-width:3}
79
84
  @media (prefers-reduced-motion:no-preference){svg .edge.on.flow .line{stroke-dasharray:8 6;animation:flow 1s linear infinite}}
80
85
  @keyframes flow{to{stroke-dashoffset:-28}}
86
+ .lane rect{fill:var(--group);stroke:var(--line)}.lane-line{stroke:var(--line);stroke-width:2}
87
+ .axis line{stroke:var(--muted);stroke-width:1}.axis text{font-size:11px;fill:var(--muted)}.axis .grid{stroke:var(--line);stroke-dasharray:2 4}
88
+ .brk{stroke:var(--muted);stroke-width:1.5;fill:none}.brk-label{font-size:10px;fill:var(--muted)}
89
+ .node.event .shape{stroke-width:2;fill:var(--panel)}.node.event .bar{fill:var(--group2);stroke-width:1.5}
90
+ .node.event .leader{stroke:var(--line);stroke-width:1}.node.event.approx .shape{stroke-dasharray:3 2}.node .label.tl{font-size:12px}.node .note.tl{font-size:10px}
91
+ .node.span .rowbg{fill:transparent}.node.span:hover .rowbg,.node.span.on .rowbg{fill:var(--group)}
92
+ .node.span .bar{stroke:none;opacity:.88}.node.span.crit .bar{stroke:var(--hl);stroke-width:2.5;opacity:1}
93
+ .node.span.err .bar{stroke:var(--bad);stroke-width:2.5}.node .label.wf{font-size:12px;font-weight:500}.node .note.wf{font-size:11px}
94
+ .node .label.italic{font-style:italic}.mk-open{fill:var(--canvas);stroke:var(--edge);stroke-width:1.2}.mk-line{fill:none;stroke:var(--edge);stroke-width:1.4}
95
+ .edge.tree .line{stroke:var(--line);stroke-width:1.5}
96
+ aside table.facts{border-collapse:collapse;font-size:13px;margin:6px 0}aside table.facts th{text-align:left;color:var(--muted);font-weight:400;padding:2px 10px 2px 0}
81
97
  @media print{.bar,aside,.caption{display:none!important}.wrap{height:auto}#stage{border:0}#stage svg{position:static;height:auto}}
82
98
  </style>
83
- <script type="application/json" id="diagram-data">{"type": "architecture", "slug": "what-a-checkout-request-touches", "source_url": null, "story": [{"text": "The shopper's browser posts the cart to the Checkout API (the client itself is not in this repo).", "edges": ["pay"]}, {"text": "Each line is priced from Redis; a miss reads Postgres and caches the price for five minutes.", "edges": ["price", "miss"]}, {"text": "Stripe is charged and confirmed in the same call; anything but 'succeeded' returns 402.", "edges": ["charge"]}, {"text": "The order is inserted, then its id is published to the fulfilment queue, and the API answers the shopper.", "edges": ["miss", "publish"]}, {"text": "Later, the worker takes the id, reads the address, books the courier and marks the order booked.", "edges": ["consume", "book", "mark"]}], "nodes": [{"id": "shopper", "label": "Shopper's browser", "kind": "client", "note": null, "detail": "Nothing in this repo shows the client; the API's POST /checkout is the entry point.", "fields": [], "evidence": [], "assumed": true, "status": null, "group_label": null}, {"id": "api", "label": "Checkout API", "kind": "service", "note": "FastAPI · POST /checkout", "detail": null, "fields": [], "evidence": [{"file": "api/checkout.py", "start": 29, "end": 41, "quote": "def checkout", "verified": true, "snippet": "@app.post(\"/checkout\")\ndef checkout(cart: dict):\n total = price_cart(cart)\n intent = stripe.PaymentIntent.create(amount=total, currency=\"gbp\", confirm=True,\n payment_method=cart[\"payment_method\"])\n if intent.status != \"succeeded\":\n raise HTTPException(402, \"payment declined\")\n order_id = db.execute(\"insert into orders (email, total, payment_id) values (%s, %s, %s) returning id\",\n (cart[\"email\"], total, intent.id)).fetchone()[0]\n db.commit()\n broker.basic_publish(exchange=\"\", routing_key=\"fulfilment\",\n body=json.dumps({\"order_id\": order_id}))\n return {\"order_id\": order_id}"}, {"file": "docker-compose.yml", "start": 2, "end": 8, "verified": true, "snippet": " api:\n build: ./api\n environment:\n DATABASE_URL: postgresql://shop@db/shop\n REDIS_URL: redis://cache:6379/0\n BROKER_URL: amqp://broker\n depends_on: [db, cache, broker]"}], "assumed": false, "status": null, "group_label": "docker-compose stack"}, {"id": "cache", "label": "Price cache", "kind": "cache", "note": "Redis, 5 min TTL", "detail": null, "fields": [], "evidence": [{"file": "api/checkout.py", "start": 20, "end": 24, "quote": "ex=300", "verified": true, "snippet": " cached = prices.get(f\"price:{line['sku']}\")\n if cached is None:\n row = db.execute(\"select price from products where sku = %s\", (line[\"sku\"],)).fetchone()\n cached = row[0]\n prices.set(f\"price:{line['sku']}\", cached, ex=300)"}, {"file": "docker-compose.yml", "start": 17, "end": 18, "verified": true, "snippet": " cache:\n image: redis:7"}], "assumed": false, "status": null, "group_label": "Data"}, {"id": "db", "label": "Orders database", "kind": "database", "note": "Postgres 16", "detail": null, "fields": [], "evidence": [{"file": "api/checkout.py", "start": 22, "end": 22, "quote": "from products", "verified": true, "snippet": " row = db.execute(\"select price from products where sku = %s\", (line[\"sku\"],)).fetchone()"}, {"file": "api/checkout.py", "start": 36, "end": 36, "quote": "insert into orders", "verified": true, "snippet": " order_id = db.execute(\"insert into orders (email, total, payment_id) values (%s, %s, %s) returning id\","}, {"file": "docker-compose.yml", "start": 15, "end": 16, "verified": true, "snippet": " db:\n image: postgres:16"}], "assumed": false, "status": null, "group_label": "Data"}, {"id": "broker", "label": "fulfilment queue", "kind": "queue", "note": "RabbitMQ", "detail": null, "fields": [], "evidence": [{"file": "api/checkout.py", "start": 39, "end": 40, "quote": "routing_key=\"fulfilment\"", "verified": true, "snippet": " broker.basic_publish(exchange=\"\", routing_key=\"fulfilment\",\n body=json.dumps({\"order_id\": order_id}))"}, {"file": "docker-compose.yml", "start": 19, "end": 20, "verified": true, "snippet": " broker:\n image: rabbitmq:3"}], "assumed": false, "status": null, "group_label": "docker-compose stack"}, {"id": "worker", "label": "Fulfilment worker", "kind": "worker", "note": null, "detail": null, "fields": [], "evidence": [{"file": "worker/fulfil.py", "start": 13, "end": 19, "quote": "def on_message", "verified": true, "snippet": "def on_message(ch, method, properties, body):\n order_id = json.loads(body)[\"order_id\"]\n address = db.execute(\"select address from orders where id = %s\", (order_id,)).fetchone()[0]\n booking = requests.post(\"https://courier.example/v2/bookings\", json={\"ref\": order_id, \"to\": address}, timeout=10)\n db.execute(\"update orders set status = 'booked', courier_ref = %s where id = %s\", (booking.json()[\"ref\"], order_id))\n db.commit()\n ch.basic_ack(method.delivery_tag)"}, {"file": "docker-compose.yml", "start": 9, "end": 14, "verified": true, "snippet": " worker:\n build: ./worker\n environment:\n DATABASE_URL: postgresql://shop@db/shop\n BROKER_URL: amqp://broker\n depends_on: [db, broker]"}], "assumed": false, "status": null, "group_label": "docker-compose stack"}, {"id": "stripe", "label": "Stripe", "kind": "external", "note": "PaymentIntent", "detail": null, "fields": [], "evidence": [{"file": "api/checkout.py", "start": 32, "end": 35, "quote": "stripe.PaymentIntent.create", "verified": true, "snippet": " intent = stripe.PaymentIntent.create(amount=total, currency=\"gbp\", confirm=True,\n payment_method=cart[\"payment_method\"])\n if intent.status != \"succeeded\":\n raise HTTPException(402, \"payment declined\")"}], "assumed": false, "status": null, "group_label": null}, {"id": "courier", "label": "Courier API", "kind": "external", "note": "courier.example", "detail": null, "fields": [], "evidence": [{"file": "worker/fulfil.py", "start": 16, "end": 16, "quote": "courier.example/v2/bookings", "verified": true, "snippet": " booking = requests.post(\"https://courier.example/v2/bookings\", json={\"ref\": order_id, \"to\": address}, timeout=10)"}], "assumed": false, "status": null, "group_label": null}], "edges": [{"id": "pay", "from": "shopper", "to": "api", "label": "POST /checkout", "detail": null, "evidence": [], "assumed": true, "status": null}, {"id": "price", "from": "api", "to": "cache", "label": "get price:\u003csku\u003e", "detail": null, "evidence": [{"file": "api/checkout.py", "start": 20, "end": 20, "verified": true, "snippet": " cached = prices.get(f\"price:{line['sku']}\")"}], "assumed": false, "status": null}, {"id": "miss", "from": "api", "to": "db", "label": "price on cache miss, insert order", "detail": null, "evidence": [{"file": "api/checkout.py", "start": 22, "end": 22, "verified": true, "snippet": " row = db.execute(\"select price from products where sku = %s\", (line[\"sku\"],)).fetchone()"}, {"file": "api/checkout.py", "start": 36, "end": 38, "verified": true, "snippet": " order_id = db.execute(\"insert into orders (email, total, payment_id) values (%s, %s, %s) returning id\",\n (cart[\"email\"], total, intent.id)).fetchone()[0]\n db.commit()"}], "assumed": false, "status": null}, {"id": "charge", "from": "api", "to": "stripe", "label": "create + confirm payment", "detail": null, "evidence": [{"file": "api/checkout.py", "start": 32, "end": 35, "verified": true, "snippet": " intent = stripe.PaymentIntent.create(amount=total, currency=\"gbp\", confirm=True,\n payment_method=cart[\"payment_method\"])\n if intent.status != \"succeeded\":\n raise HTTPException(402, \"payment declined\")"}], "assumed": false, "status": null}, {"id": "publish", "from": "api", "to": "broker", "label": "publish order_id", "detail": null, "evidence": [{"file": "api/checkout.py", "start": 39, "end": 40, "verified": true, "snippet": " broker.basic_publish(exchange=\"\", routing_key=\"fulfilment\",\n body=json.dumps({\"order_id\": order_id}))"}], "assumed": false, "status": null}, {"id": "consume", "from": "broker", "to": "worker", "label": "consume", "detail": null, "evidence": [{"file": "worker/fulfil.py", "start": 22, "end": 22, "quote": "basic_consume", "verified": true, "snippet": "channel.basic_consume(queue=\"fulfilment\", on_message_callback=on_message)"}], "assumed": false, "status": null}, {"id": "book", "from": "worker", "to": "courier", "label": "book courier", "detail": null, "evidence": [{"file": "worker/fulfil.py", "start": 16, "end": 16, "verified": true, "snippet": " booking = requests.post(\"https://courier.example/v2/bookings\", json={\"ref\": order_id, \"to\": address}, timeout=10)"}], "assumed": false, "status": null}, {"id": "mark", "from": "worker", "to": "db", "label": "read address, mark booked", "detail": null, "evidence": [{"file": "worker/fulfil.py", "start": 15, "end": 15, "verified": true, "snippet": " address = db.execute(\"select address from orders where id = %s\", (order_id,)).fetchone()[0]"}, {"file": "worker/fulfil.py", "start": 17, "end": 17, "verified": true, "snippet": " db.execute(\"update orders set status = 'booked', courier_ref = %s where id = %s\", (booking.json()[\"ref\"], order_id))"}], "assumed": false, "status": null}], "mermaid": "flowchart LR\n shopper[\"Shopper's browser\"]\n stripe[\"Stripe\"]\n courier[\"Courier API\"]\n subgraph compose[\"docker-compose stack\"]\n api[\"Checkout API\"]\n broker[[\"fulfilment queue\"]]\n worker[\"Fulfilment worker\"]\n subgraph data[\"Data\"]\n cache[(\"Price cache\")]\n db[(\"Orders database\")]\n end\n end\n shopper -.-\u003e|\"POST /checkout\"| api\n api --\u003e|\"get price:\u003csku\u003e\"| cache\n api --\u003e|\"price on cache miss, insert order\"| db\n api ==\u003e|\"create + confirm payment\"| stripe\n api -.-\u003e|\"publish order_id\"| broker\n broker -.-\u003e|\"consume\"| worker\n worker --\u003e|\"book courier\"| courier\n worker --\u003e|\"read address, mark booked\"| db\n classDef assumed stroke-dasharray: 5 4\n class shopper assumed\n"}</script>
99
+ <script type="application/json" id="diagram-data">{"type": "architecture", "slug": "what-a-checkout-request-touches", "source_url": null, "story": [{"text": "The shopper's browser posts the cart to the Checkout API (the client itself is not in this repo).", "edges": ["pay"]}, {"text": "Each line is priced from Redis; a miss reads Postgres and caches the price for five minutes.", "edges": ["price", "miss"]}, {"text": "Stripe is charged and confirmed in the same call; anything but 'succeeded' returns 402.", "edges": ["charge"]}, {"text": "The order is inserted, then its id is published to the fulfilment queue, and the API answers the shopper.", "edges": ["miss", "publish"]}, {"text": "Later, the worker takes the id, reads the address, books the courier and marks the order booked.", "edges": ["consume", "book", "mark"]}], "nodes": [{"id": "shopper", "label": "Shopper's browser", "kind": "client", "note": null, "detail": "Nothing in this repo shows the client; the API's POST /checkout is the entry point.", "fields": [], "evidence": [], "assumed": true, "status": null, "facts": null, "group_label": null}, {"id": "api", "label": "Checkout API", "kind": "service", "note": "FastAPI · POST /checkout", "detail": null, "fields": [], "evidence": [{"file": "api/checkout.py", "start": 29, "end": 41, "quote": "def checkout", "verified": true, "snippet": "@app.post(\"/checkout\")\ndef checkout(cart: dict):\n total = price_cart(cart)\n intent = stripe.PaymentIntent.create(amount=total, currency=\"gbp\", confirm=True,\n payment_method=cart[\"payment_method\"])\n if intent.status != \"succeeded\":\n raise HTTPException(402, \"payment declined\")\n order_id = db.execute(\"insert into orders (email, total, payment_id) values (%s, %s, %s) returning id\",\n (cart[\"email\"], total, intent.id)).fetchone()[0]\n db.commit()\n broker.basic_publish(exchange=\"\", routing_key=\"fulfilment\",\n body=json.dumps({\"order_id\": order_id}))\n return {\"order_id\": order_id}"}, {"file": "docker-compose.yml", "start": 2, "end": 8, "verified": true, "snippet": " api:\n build: ./api\n environment:\n DATABASE_URL: postgresql://shop@db/shop\n REDIS_URL: redis://cache:6379/0\n BROKER_URL: amqp://broker\n depends_on: [db, cache, broker]"}], "assumed": false, "status": null, "facts": null, "group_label": "docker-compose stack"}, {"id": "cache", "label": "Price cache", "kind": "cache", "note": "Redis, 5 min TTL", "detail": null, "fields": [], "evidence": [{"file": "api/checkout.py", "start": 20, "end": 24, "quote": "ex=300", "verified": true, "snippet": " cached = prices.get(f\"price:{line['sku']}\")\n if cached is None:\n row = db.execute(\"select price from products where sku = %s\", (line[\"sku\"],)).fetchone()\n cached = row[0]\n prices.set(f\"price:{line['sku']}\", cached, ex=300)"}, {"file": "docker-compose.yml", "start": 17, "end": 18, "verified": true, "snippet": " cache:\n image: redis:7"}], "assumed": false, "status": null, "facts": null, "group_label": "Data"}, {"id": "db", "label": "Orders database", "kind": "database", "note": "Postgres 16", "detail": null, "fields": [], "evidence": [{"file": "api/checkout.py", "start": 22, "end": 22, "quote": "from products", "verified": true, "snippet": " row = db.execute(\"select price from products where sku = %s\", (line[\"sku\"],)).fetchone()"}, {"file": "api/checkout.py", "start": 36, "end": 36, "quote": "insert into orders", "verified": true, "snippet": " order_id = db.execute(\"insert into orders (email, total, payment_id) values (%s, %s, %s) returning id\","}, {"file": "docker-compose.yml", "start": 15, "end": 16, "verified": true, "snippet": " db:\n image: postgres:16"}], "assumed": false, "status": null, "facts": null, "group_label": "Data"}, {"id": "broker", "label": "fulfilment queue", "kind": "queue", "note": "RabbitMQ", "detail": null, "fields": [], "evidence": [{"file": "api/checkout.py", "start": 39, "end": 40, "quote": "routing_key=\"fulfilment\"", "verified": true, "snippet": " broker.basic_publish(exchange=\"\", routing_key=\"fulfilment\",\n body=json.dumps({\"order_id\": order_id}))"}, {"file": "docker-compose.yml", "start": 19, "end": 20, "verified": true, "snippet": " broker:\n image: rabbitmq:3"}], "assumed": false, "status": null, "facts": null, "group_label": "docker-compose stack"}, {"id": "worker", "label": "Fulfilment worker", "kind": "worker", "note": null, "detail": null, "fields": [], "evidence": [{"file": "worker/fulfil.py", "start": 13, "end": 19, "quote": "def on_message", "verified": true, "snippet": "def on_message(ch, method, properties, body):\n order_id = json.loads(body)[\"order_id\"]\n address = db.execute(\"select address from orders where id = %s\", (order_id,)).fetchone()[0]\n booking = requests.post(\"https://courier.example/v2/bookings\", json={\"ref\": order_id, \"to\": address}, timeout=10)\n db.execute(\"update orders set status = 'booked', courier_ref = %s where id = %s\", (booking.json()[\"ref\"], order_id))\n db.commit()\n ch.basic_ack(method.delivery_tag)"}, {"file": "docker-compose.yml", "start": 9, "end": 14, "verified": true, "snippet": " worker:\n build: ./worker\n environment:\n DATABASE_URL: postgresql://shop@db/shop\n BROKER_URL: amqp://broker\n depends_on: [db, broker]"}], "assumed": false, "status": null, "facts": null, "group_label": "docker-compose stack"}, {"id": "stripe", "label": "Stripe", "kind": "external", "note": "PaymentIntent", "detail": null, "fields": [], "evidence": [{"file": "api/checkout.py", "start": 32, "end": 35, "quote": "stripe.PaymentIntent.create", "verified": true, "snippet": " intent = stripe.PaymentIntent.create(amount=total, currency=\"gbp\", confirm=True,\n payment_method=cart[\"payment_method\"])\n if intent.status != \"succeeded\":\n raise HTTPException(402, \"payment declined\")"}], "assumed": false, "status": null, "facts": null, "group_label": null}, {"id": "courier", "label": "Courier API", "kind": "external", "note": "courier.example", "detail": null, "fields": [], "evidence": [{"file": "worker/fulfil.py", "start": 16, "end": 16, "quote": "courier.example/v2/bookings", "verified": true, "snippet": " booking = requests.post(\"https://courier.example/v2/bookings\", json={\"ref\": order_id, \"to\": address}, timeout=10)"}], "assumed": false, "status": null, "facts": null, "group_label": null}], "edges": [{"id": "pay", "from": "shopper", "to": "api", "label": "POST /checkout", "detail": null, "evidence": [], "assumed": true, "status": null}, {"id": "price", "from": "api", "to": "cache", "label": "get price:\u003csku\u003e", "detail": null, "evidence": [{"file": "api/checkout.py", "start": 20, "end": 20, "verified": true, "snippet": " cached = prices.get(f\"price:{line['sku']}\")"}], "assumed": false, "status": null}, {"id": "miss", "from": "api", "to": "db", "label": "price on cache miss, insert order", "detail": null, "evidence": [{"file": "api/checkout.py", "start": 22, "end": 22, "verified": true, "snippet": " row = db.execute(\"select price from products where sku = %s\", (line[\"sku\"],)).fetchone()"}, {"file": "api/checkout.py", "start": 36, "end": 38, "verified": true, "snippet": " order_id = db.execute(\"insert into orders (email, total, payment_id) values (%s, %s, %s) returning id\",\n (cart[\"email\"], total, intent.id)).fetchone()[0]\n db.commit()"}], "assumed": false, "status": null}, {"id": "charge", "from": "api", "to": "stripe", "label": "create + confirm payment", "detail": null, "evidence": [{"file": "api/checkout.py", "start": 32, "end": 35, "verified": true, "snippet": " intent = stripe.PaymentIntent.create(amount=total, currency=\"gbp\", confirm=True,\n payment_method=cart[\"payment_method\"])\n if intent.status != \"succeeded\":\n raise HTTPException(402, \"payment declined\")"}], "assumed": false, "status": null}, {"id": "publish", "from": "api", "to": "broker", "label": "publish order_id", "detail": null, "evidence": [{"file": "api/checkout.py", "start": 39, "end": 40, "verified": true, "snippet": " broker.basic_publish(exchange=\"\", routing_key=\"fulfilment\",\n body=json.dumps({\"order_id\": order_id}))"}], "assumed": false, "status": null}, {"id": "consume", "from": "broker", "to": "worker", "label": "consume", "detail": null, "evidence": [{"file": "worker/fulfil.py", "start": 22, "end": 22, "quote": "basic_consume", "verified": true, "snippet": "channel.basic_consume(queue=\"fulfilment\", on_message_callback=on_message)"}], "assumed": false, "status": null}, {"id": "book", "from": "worker", "to": "courier", "label": "book courier", "detail": null, "evidence": [{"file": "worker/fulfil.py", "start": 16, "end": 16, "verified": true, "snippet": " booking = requests.post(\"https://courier.example/v2/bookings\", json={\"ref\": order_id, \"to\": address}, timeout=10)"}], "assumed": false, "status": null}, {"id": "mark", "from": "worker", "to": "db", "label": "read address, mark booked", "detail": null, "evidence": [{"file": "worker/fulfil.py", "start": 15, "end": 15, "verified": true, "snippet": " address = db.execute(\"select address from orders where id = %s\", (order_id,)).fetchone()[0]"}, {"file": "worker/fulfil.py", "start": 17, "end": 17, "verified": true, "snippet": " db.execute(\"update orders set status = 'booked', courier_ref = %s where id = %s\", (booking.json()[\"ref\"], order_id))"}], "assumed": false, "status": null}], "mermaid": "flowchart LR\n shopper[\"Shopper's browser\"]\n stripe[\"Stripe\"]\n courier[\"Courier API\"]\n subgraph compose[\"docker-compose stack\"]\n api[\"Checkout API\"]\n broker[[\"fulfilment queue\"]]\n worker[\"Fulfilment worker\"]\n subgraph data[\"Data\"]\n cache[(\"Price cache\")]\n db[(\"Orders database\")]\n end\n end\n shopper -.-\u003e|\"POST /checkout\"| api\n api --\u003e|\"get price:\u003csku\u003e\"| cache\n api --\u003e|\"price on cache miss, insert order\"| db\n api ==\u003e|\"create + confirm payment\"| stripe\n api -.-\u003e|\"publish order_id\"| broker\n broker -.-\u003e|\"consume\"| worker\n worker --\u003e|\"book courier\"| courier\n worker --\u003e|\"read address, mark booked\"| db\n classDef assumed stroke-dasharray: 5 4\n class shopper assumed\n", "words": {"out": "Goes to", "in": "Comes from", "reach_out": "Everything it reaches", "reach_in": "Everything that reaches it"}, "auto_steps": []}</script>
84
100
  <script>
85
101
  (function(){
86
- var DATA=JSON.parse(document.getElementById('diagram-data').textContent);
102
+ var DATA=JSON.parse(document.getElementById('diagram-data').textContent);var WD=DATA.words;
87
103
  var svg=document.getElementById('map'),stage=document.getElementById('stage'),panel=document.getElementById('panel');
88
104
  var root=document.documentElement;
89
105
  var pref=localStorage.getItem('idg-theme')||root.getAttribute('data-theme-default');
@@ -127,11 +143,12 @@ function show(kind,id){var x=kind==='n'?nodes.get(id):edges.get(id);if(!x)return
127
143
  if(kind==='n'){h+='<h3>'+esc(x.label)+'</h3><div class="kind">'+esc(x.kind)+(x.group_label?' · '+esc(x.group_label):'')+(x.assumed?' · <span style="color:var(--warn)">assumption</span>':'')+(x.status?' · '+esc(x.status):'')+'</div>';
128
144
  if(x.note)h+='<p>'+esc(x.note)+'</p>';if(x.detail)h+='<p>'+esc(x.detail)+'</p>';
129
145
  if(x.fields&&x.fields.length)h+='<ul>'+x.fields.map(function(f){return '<li><code>'+esc(f.name)+'</code> '+esc(f.type||'')+(f.key?' <b>'+esc([].concat(f.key).join('/').toUpperCase())+'</b>':'')+(f.references?' → '+esc(f.references):'')+(f.note?' — '+esc(f.note):'')+'</li>'}).join('')+'</ul>';
130
- h+='<div class="acts"><button data-act="up">What it depends on</button><button data-act="down">What depends on it</button><button data-act="near">Neighbours</button></div>';
146
+ if(x.facts&&x.facts.length)h+='<table class="facts">'+x.facts.map(function(f){return '<tr><th>'+esc(f[0])+'</th><td>'+esc(f[1])+'</td></tr>'}).join('')+'</table>';
147
+ h+='<div class="acts"><button data-act="out">'+esc(WD.reach_out)+'</button><button data-act="in">'+esc(WD.reach_in)+'</button><button data-act="near">Neighbours</button></div>';
131
148
  h+='<p class="muted" style="font-size:12px">Shift-click another part to see the path between them.</p>';
132
149
  var o=outE.get(id).map(function(i){var e=edges.get(i);return '<li><a class="jump" data-n="'+esc(e.to)+'">'+esc(nodes.get(e.to).label)+'</a>'+(e.label?' — '+esc(e.label):'')+'</li>'}).join('');
133
150
  var n=inE.get(id).map(function(i){var e=edges.get(i);return '<li><a class="jump" data-n="'+esc(e.from)+'">'+esc(nodes.get(e.from).label)+'</a>'+(e.label?' — '+esc(e.label):'')+'</li>'}).join('');
134
- if(o)h+='<b>Goes to</b><ul>'+o+'</ul>';if(n)h+='<b>Comes from</b><ul>'+n+'</ul>';
151
+ if(o)h+='<b>'+esc(WD.out)+'</b><ul>'+o+'</ul>';if(n)h+='<b>'+esc(WD.in)+'</b><ul>'+n+'</ul>';
135
152
  h+='<b>Evidence</b>'+evHtml(x.evidence);
136
153
  var nb=new Set([id]);outE.get(id).concat(inE.get(id)).forEach(function(i){nb.add(edges.get(i).from);nb.add(edges.get(i).to)});light(Array.from(nb),outE.get(id).concat(inE.get(id)));el('n',id).classList.add('sel');
137
154
  }else{h+='<h3>'+esc(nodes.get(x.from).label)+' → '+esc(nodes.get(x.to).label)+'</h3><div class="kind">'+(x.label?esc(x.label):'connection')+(x.assumed?' · <span style="color:var(--warn)">assumption</span>':'')+(x.status?' · '+esc(x.status):'')+'</div>';
@@ -140,7 +157,7 @@ function show(kind,id){var x=kind==='n'?nodes.get(id):edges.get(id);if(!x)return
140
157
  panel.querySelector('.close').onclick=function(){panel.classList.remove('open');clear();selected=null};
141
158
  panel.querySelectorAll('.jump').forEach(function(a){a.onclick=function(){show('n',a.getAttribute('data-n'))}});
142
159
  panel.querySelectorAll('[data-act]').forEach(function(b){b.onclick=function(){var a=b.getAttribute('data-act');
143
- if(a==='near'){show('n',id);return}var r=reach(id,a==='down'?1:-1);light(r.n,r.e,true);el('n',id).classList.add('sel')}})}
160
+ if(a==='near'){show('n',id);return}var r=reach(id,a==='out'?1:-1);light(r.n,r.e,true);el('n',id).classList.add('sel')}})}
144
161
  svg.addEventListener('click',function(e){var g=e.target.closest('.node');if(g){var id=g.getAttribute('data-id');
145
162
  if(e.shiftKey&&selected&&selected!==id){var p=path(selected,id,true)||path(selected,id,false);if(p){light(p.n,p.e,true)}return}show('n',id);return}
146
163
  var ed=e.target.closest('.edge');if(ed){show('e',ed.getAttribute('data-id'));return}});
@@ -163,7 +180,7 @@ document.getElementById('xpng').onclick=function(){var img=new Image();img.onloa
163
180
  img.src='data:image/svg+xml;charset=utf-8,'+encodeURIComponent(standalone())};
164
181
  document.getElementById('xmmd').onclick=function(){var t=DATA.mermaid;if(navigator.clipboard)navigator.clipboard.writeText(t).catch(function(){});download(base+'.mmd','text/plain',t)};
165
182
  var tour=null,cap=document.getElementById('caption');
166
- var steps=DATA.story&&DATA.story.length?DATA.story:(DATA.type==='sequence'?DATA.edges.map(function(m,i){return {text:(i+1)+'. '+nodes.get(m.from).label+' → '+nodes.get(m.to).label+(m.label?': '+m.label:''),edges:[m.id],nodes:[m.from,m.to]}}):[]);
183
+ var steps=DATA.story&&DATA.story.length?DATA.story:(DATA.auto_steps&&DATA.auto_steps.length)?DATA.auto_steps:(DATA.type==='sequence'?DATA.edges.map(function(m,i){return {text:(i+1)+'. '+nodes.get(m.from).label+' → '+nodes.get(m.to).label+(m.label?': '+m.label:''),edges:[m.id],nodes:[m.from,m.to]}}):[]);
167
184
  var tb=document.getElementById('tour');if(!steps.length){tb.style.display='none'}
168
185
  function step(d){tour.i=Math.max(0,Math.min(steps.length-1,tour.i+d));var s=steps[tour.i];
169
186
  light((s.nodes||[]).concat((s.edges||[]).map(function(i){return edges.get(i)?edges.get(i).from:null}).filter(Boolean)).concat((s.edges||[]).map(function(i){return edges.get(i)?edges.get(i).to:null}).filter(Boolean)),s.edges||[],true);