skilldrop-cli 0.16.21 → 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 (55) hide show
  1. package/bin/skilldrop.js +5 -3
  2. package/catalogue.json +1 -0
  3. package/model-routing.json +4 -0
  4. package/package.json +1 -1
  5. package/packs/solution-architect/skills/interactive-diagram/SKILL.md +160 -0
  6. package/packs/solution-architect/skills/interactive-diagram/evals/eval_queries.json +12 -0
  7. package/packs/solution-architect/skills/interactive-diagram/evals/evals.json +164 -0
  8. package/packs/solution-architect/skills/interactive-diagram/evals/files/1/README.md +11 -0
  9. package/packs/solution-architect/skills/interactive-diagram/evals/files/1/api/app.py +43 -0
  10. package/packs/solution-architect/skills/interactive-diagram/evals/files/1/docker-compose.yml +20 -0
  11. package/packs/solution-architect/skills/interactive-diagram/evals/files/1/web/checkout.js +9 -0
  12. package/packs/solution-architect/skills/interactive-diagram/evals/files/1/worker/worker.py +23 -0
  13. package/packs/solution-architect/skills/interactive-diagram/evals/files/10/build-4127-timing.json +179 -0
  14. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/AbstractSensor.java +36 -0
  15. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/AlertService.java +15 -0
  16. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/Greenhouse.java +36 -0
  17. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/Sensor.java +10 -0
  18. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/SoilMoistureSensor.java +25 -0
  19. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/TemperatureSensor.java +19 -0
  20. package/packs/solution-architect/skills/interactive-diagram/evals/files/11/src/org/example/greenhouse/Zone.java +26 -0
  21. package/packs/solution-architect/skills/interactive-diagram/evals/files/3/schema.sql +35 -0
  22. package/packs/solution-architect/skills/interactive-diagram/evals/files/4/README.md +26 -0
  23. package/packs/solution-architect/skills/interactive-diagram/evals/files/4/config/default.json +29 -0
  24. package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/clients/transcription.js +25 -0
  25. package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/db.js +16 -0
  26. package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/handlers/storageWebhook.js +43 -0
  27. package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/jobs/transcribeEpisode.js +28 -0
  28. package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/mailer.js +18 -0
  29. package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/server.js +32 -0
  30. package/packs/solution-architect/skills/interactive-diagram/evals/files/4/src/worker.js +32 -0
  31. package/packs/solution-architect/skills/interactive-diagram/evals/files/6/notes.txt +7 -0
  32. package/packs/solution-architect/skills/interactive-diagram/evals/files/6/trace-quote-slow.jsonl +13 -0
  33. package/packs/solution-architect/skills/interactive-diagram/evals/files/7/oncall-notes.md +16 -0
  34. package/packs/solution-architect/skills/interactive-diagram/evals/files/7/system-log.txt +14 -0
  35. package/packs/solution-architect/skills/interactive-diagram/evals/files/8/run-4471.json +151 -0
  36. package/packs/solution-architect/skills/interactive-diagram/evals/files/9/chat-export.txt +16 -0
  37. package/packs/solution-architect/skills/interactive-diagram/evals/files/9/lesson-sync.log +24 -0
  38. package/packs/solution-architect/skills/interactive-diagram/evals/files/9/ticket-8812.csv +6 -0
  39. package/packs/solution-architect/skills/interactive-diagram/examples/checkout-map.html +203 -0
  40. package/packs/solution-architect/skills/interactive-diagram/examples/checkout-map.json +48 -0
  41. package/packs/solution-architect/skills/interactive-diagram/examples/checkout-map.md +31 -0
  42. package/packs/solution-architect/skills/interactive-diagram/examples/checkout-trace.html +203 -0
  43. package/packs/solution-architect/skills/interactive-diagram/examples/checkout-trace.json +110 -0
  44. package/packs/solution-architect/skills/interactive-diagram/examples/incident-timeline.html +203 -0
  45. package/packs/solution-architect/skills/interactive-diagram/examples/incident-timeline.json +21 -0
  46. package/packs/solution-architect/skills/interactive-diagram/examples/inputs/checkout/api/checkout.py +41 -0
  47. package/packs/solution-architect/skills/interactive-diagram/examples/inputs/checkout/docker-compose.yml +20 -0
  48. package/packs/solution-architect/skills/interactive-diagram/examples/inputs/checkout/worker/fulfil.py +23 -0
  49. package/packs/solution-architect/skills/interactive-diagram/examples/inputs/incident/oncall-notes.md +14 -0
  50. package/packs/solution-architect/skills/interactive-diagram/examples/inputs/trace/checkout-trace.jsonl +9 -0
  51. package/packs/solution-architect/skills/interactive-diagram/examples/leave-approval.html +203 -0
  52. package/packs/solution-architect/skills/interactive-diagram/examples/leave-approval.json +28 -0
  53. package/packs/solution-architect/skills/interactive-diagram/manifest.json +39 -0
  54. package/packs/solution-architect/skills/interactive-diagram/reference.md +231 -0
  55. package/packs/solution-architect/skills/interactive-diagram/scripts/build_diagram.py +2605 -0
@@ -0,0 +1,203 @@
1
+ <!doctype html>
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-KdTstAM29gVIP104X7Tju5/PTeQKbRDMMG1TjJdsOyI='; style-src 'unsafe-inline'; img-src data: blob:; base-uri 'none'; form-action 'none'">
4
+ <meta name="viewport" content="width=device-width,initial-scale=1">
5
+ <!-- One self-contained file: no external scripts, styles, fonts or requests. The policy above makes the browser refuse any. -->
6
+ <title>Card payments failing, 3 October</title><meta name="generator" content="interactive-diagram (skilldrop)"></head>
7
+ <body>
8
+ <header class="top"><h1>Card payments failing, 3 October</h1><p class="q">What happened, and how long did it take anyone to notice?</p><p class="sum">Errors climbed seven minutes after the 2.14 rollout, but the page fired 22 minutes later because the alert waits for a fixed 5% to hold. From the page, rollback took 28 minutes.</p><div class="findings"><b>What it shows</b><ul><li>From 03 Oct 09:12 to 06 Oct 14:00: 3d 4h.</li><li>Longest quiet stretch: 3d 3h, between Status page: resolved (03 Oct 10:30) and Postmortem (06 Oct 14:00).</li></ul></div><p class="muted" style="font-size:12px;margin:6px 0 0">11 of 11 file citations checked against the repository</p></header>
9
+ <section class="text" id="outline" aria-label="The diagram as text">
10
+ <details open><summary>The diagram as text</summary><h2>Events, in time order</h2><p>03 Oct 09:12 to 06 Oct 14:00 (3d 4h).</p><ol><li><b>03 Oct 09:12</b> v2.14 rolled out <span class="muted">(Deploys)</span> <span class="ev">(<code>oncall-notes.md:5</code> ✓)</span></li><li><b>03 Oct 09:19</b> Card errors pass 5% <span class="muted">(Monitoring)</span> <span class="ev">(<code>oncall-notes.md:6</code> ✓)</span></li><li><b>03 Oct 09:41</b> Pager fires <span class="muted">(Monitoring)</span> <span class="ev">(<code>oncall-notes.md:7</code> ✓)</span></li><li><b>03 Oct 09:44</b> Ana acknowledges <span class="muted">(On-call)</span> <span class="ev">(<code>oncall-notes.md:8</code> ✓)</span></li><li><b>03 Oct 09:51</b> Rollout named as the cause <span class="muted">(On-call)</span> <span class="ev">(<code>oncall-notes.md:9</code> ✓)</span></li><li><b>03 Oct 09:58</b> Rollback to v2.13 <span class="muted">(Deploys)</span> <span class="ev">(<code>oncall-notes.md:10-11</code> ✓)</span></li><li><b>03 Oct 10:11</b> Errors back under 0.5% <span class="muted">(Monitoring)</span> <span class="ev">(<code>oncall-notes.md:12</code> ✓)</span></li><li><b>03 Oct 10:30</b> Status page: resolved <span class="muted">(On-call)</span> <span class="ev">(<code>oncall-notes.md:13</code> ✓)</span></li><li><b>06 Oct 14:00</b> Postmortem <span class="muted">(On-call)</span> <span class="ev">(<code>oncall-notes.md:14</code> ✓)</span></li></ol><h2>Notes</h2><ul><li>Customer impact (how many payments failed) is not in the notes, so it is not on the timeline.</li></ul></details></section>
11
+ <nav class="bar" aria-label="Diagram controls">
12
+ <input id="find" type="search" placeholder="Find a part…" aria-label="Find a part">
13
+ <button id="zin" title="Zoom in">+</button><button id="zout" title="Zoom out">−</button><button id="zfit" title="Fit to screen">Fit</button>
14
+ <span class="sep"></span><button id="tour">Walk through</button><button id="prev" title="Previous step">←</button><button id="next" title="Next step">→</button>
15
+ <span class="sep"></span><button id="xsvg">SVG</button><button id="xpng">PNG</button><button id="xmmd" title="Copy and download Mermaid">Mermaid</button>
16
+ <button id="theme" title="Light or dark">◐</button>
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
+ <div class="legend"><span>● event</span><span>◆ milestone</span><span>▬ lasted from … to</span><span>// a quiet stretch, shortened</span></div>
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 1401 368" width="1401" height="368" role="group" aria-label="Card payments failing, 3 October"><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="1401" height="368"/><g class="lane"><rect x="20" y="70.0" width="1341.5" height="68.0" rx="8"/><text class="glabel" x="32" y="124.0">Deploys</text><line class="lane-line" x1="180.0" y1="120.0" x2="1300.0" y2="120.0"/></g><g class="lane"><rect x="20" y="148.0" width="1341.5" height="68.0" rx="8"/><text class="glabel" x="32" y="202.0">Monitoring</text><line class="lane-line" x1="180.0" y1="198.0" x2="1300.0" y2="198.0"/></g><g class="lane"><rect x="20" y="226.0" width="1341.5" height="102.0" rx="8"/><text class="glabel" x="32" y="314.0">On-call</text><line class="lane-line" x1="180.0" y1="310.0" x2="1300.0" y2="310.0"/></g><g class="axis"><line x1="294.2" y1="44" x2="294.2" y2="52"/><text x="294.2" y="38" text-anchor="middle">03 Oct 09:20</text><line x1="424.5" y1="44" x2="424.5" y2="52"/><text x="424.5" y="38" text-anchor="middle">09:30</text><line x1="554.7" y1="44" x2="554.7" y2="52"/><text x="554.7" y="38" text-anchor="middle">09:40</text><line x1="685.0" y1="44" x2="685.0" y2="52"/><text x="685.0" y="38" text-anchor="middle">09:50</text><line x1="815.2" y1="44" x2="815.2" y2="52"/><text x="815.2" y="38" text-anchor="middle">10:00</text><line x1="945.5" y1="44" x2="945.5" y2="52"/><text x="945.5" y="38" text-anchor="middle">10:10</text><line x1="1075.7" y1="44" x2="1075.7" y2="52"/><text x="1075.7" y="38" text-anchor="middle">10:20</text><line x1="1206.0" y1="44" x2="1206.0" y2="52"/><text x="1206.0" y="38" text-anchor="middle">10:30</text><line x1="1270.0" y1="44" x2="1270.0" y2="52"/><text x="1270.0" y="38" text-anchor="middle">06 Oct 14:00</text><path class="brk" d="M1231.0,40 l6,16 M1239.0,40 l6,16"/><text class="brk-label" x="1238.0" y="66" text-anchor="middle">3d 3h later</text><line x1="180.0" y1="48" x2="1300.0" y2="48"/></g><g class="edge s-solid" id="e-e1" data-id="e1" data-from="deploy" data-to="climb"><title>caused</title><path class="hit" d="M190.0,120.0 L281.2,198.0"/><path class="line" d="M190.0,120.0 L281.2,198.0" marker-end="url(#arrow)"/></g><g class="edge s-solid" id="e-e2" data-id="e2" data-from="climb" data-to="page"><title>detected 22 min later</title><path class="hit" d="M281.2,198.0 L567.7,198.0"/><path class="line" d="M281.2,198.0 L567.7,198.0" marker-end="url(#arrow)"/></g><g class="node event c-amber k-change" id="n-deploy" data-id="deploy" tabindex="0" role="button" aria-label="03 Oct 09:12 v2.14 rolled out"><title>03 Oct 09:12 — v2.14 rolled out</title><line class="leader" x1="190.0" y1="113.0" x2="190.0" y2="98.0"/><circle class="shape" cx="190.0" cy="120.0" r="6.5"/><text class="label tl" x="187.0" y="94.0">v2.14 rolled out</text><text class="note tl" x="187.0" y="81.0">03 Oct 09:12</text></g><g class="node event c-rose k-alert" id="n-climb" data-id="climb" tabindex="0" role="button" aria-label="03 Oct 09:19 Card errors pass 5%"><title>03 Oct 09:19 — Card errors pass 5%</title><line class="leader" x1="281.2" y1="191.0" x2="281.2" y2="176.0"/><circle class="shape" cx="281.2" cy="198.0" r="6.5"/><text class="label tl" x="278.2" y="172.0">Card errors pass 5%</text><text class="note tl" x="278.2" y="159.0">03 Oct 09:19</text></g><g class="node event c-rose k-alert" id="n-page" data-id="page" tabindex="0" role="button" aria-label="03 Oct 09:41 Pager fires"><title>03 Oct 09:41 — Pager fires</title><line class="leader" x1="567.7" y1="191.0" x2="567.7" y2="176.0"/><circle class="shape" cx="567.7" cy="198.0" r="6.5"/><text class="label tl" x="564.7" y="172.0">Pager fires</text><text class="note tl" x="564.7" y="159.0">03 Oct 09:41</text></g><g class="node event c-green k-action" id="n-ack" data-id="ack" tabindex="0" role="button" aria-label="03 Oct 09:44 Ana acknowledges"><title>03 Oct 09:44 — Ana acknowledges</title><line class="leader" x1="606.8" y1="303.0" x2="606.8" y2="288.0"/><circle class="shape" cx="606.8" cy="310.0" r="6.5"/><text class="label tl" x="603.8" y="284.0">Ana acknowledges</text><text class="note tl" x="603.8" y="271.0">03 Oct 09:44</text></g><g class="node event c-green k-action" id="n-cause" data-id="cause" tabindex="0" role="button" aria-label="03 Oct 09:51 Rollout named as the cause"><title>03 Oct 09:51 — Rollout named as the cause</title><line class="leader" x1="698.0" y1="303.0" x2="698.0" y2="254.0"/><circle class="shape" cx="698.0" cy="310.0" r="6.5"/><text class="label tl" x="695.0" y="250.0">Rollout named as the cause</text><text class="note tl" x="695.0" y="237.0">03 Oct 09:51</text></g><g class="node event c-amber k-change" id="n-rollback" data-id="rollback" tabindex="0" role="button" aria-label="03 Oct 09:58 Rollback to v2.13"><title>03 Oct 09:58 — Rollback to v2.13</title><rect class="shape bar" x="789.2" y="115.0" width="143.3" height="10" rx="5"/><line class="leader" x1="789.2" y1="113.0" x2="789.2" y2="98.0"/><circle class="shape" cx="789.2" cy="120.0" r="6.5"/><text class="label tl" x="786.2" y="94.0">Rollback to v2.13</text><text class="note tl" x="786.2" y="81.0">03 Oct 09:58</text></g><g class="node event c-violet k-milestone" id="n-normal" data-id="normal" tabindex="0" role="button" aria-label="03 Oct 10:11 Errors back under 0.5%"><title>03 Oct 10:11 — Errors back under 0.5%</title><line class="leader" x1="958.5" y1="191.0" x2="958.5" y2="176.0"/><path class="shape" d="M958.5,190.0 l8,8 l-8,8 l-8,-8 z"/><text class="label tl" x="955.5" y="172.0">Errors back under 0.5%</text><text class="note tl" x="955.5" y="159.0">03 Oct 10:11</text></g><g class="node event c-green k-action" id="n-status" data-id="status" tabindex="0" role="button" aria-label="03 Oct 10:30 Status page: resolved"><title>03 Oct 10:30 — Status page: resolved</title><line class="leader" x1="1206.0" y1="303.0" x2="1206.0" y2="288.0"/><circle class="shape" cx="1206.0" cy="310.0" r="6.5"/><text class="label tl" x="1203.0" y="284.0">Status page: resolved</text><text class="note tl" x="1203.0" y="271.0">03 Oct 10:30</text></g><g class="node event c-violet k-milestone" id="n-pm" data-id="pm" tabindex="0" role="button" aria-label="06 Oct 14:00 Postmortem"><title>06 Oct 14:00 — Postmortem</title><line class="leader" x1="1270.0" y1="303.0" x2="1270.0" y2="254.0"/><path class="shape" d="M1270.0,302.0 l8,8 l-8,8 l-8,-8 z"/><text class="label tl" x="1267.0" y="250.0">Postmortem</text><text class="note tl" x="1267.0" y="237.0">06 Oct 14:00</text></g></svg></div><aside id="panel" aria-live="polite"></aside></div>
21
+ <style id="diagram-css">
22
+ :root{--bg:#f7f8fa;--panel:#ffffff;--ink:#16181d;--muted:#5b6170;--line:#d5d9e0;--edge:#6b7385;--accent:#2563eb;
23
+ --warn:#b45309;--ok:#15803d;--bad:#b91c1c;--hl:#f59e0b;--canvas:#fbfcfd;--group:#eef1f6;--group2:#e6eaf2;--shadow:0 1px 2px rgba(0,0,0,.06)}
24
+ :root[data-theme=dark]{--bg:#0f1115;--panel:#171a21;--ink:#e7e9ee;--muted:#9aa1b2;--line:#2b303b;--edge:#8b93a7;--accent:#60a5fa;
25
+ --warn:#f59e0b;--ok:#4ade80;--bad:#f87171;--hl:#fbbf24;--canvas:#12151b;--group:#1a1e27;--group2:#20252f;--shadow:none}
26
+ *{box-sizing:border-box}html,body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
27
+ body{display:flex;flex-direction:column}body>*{flex:none}section.text{order:10}
28
+ header.top{padding:18px 24px 10px;max-width:1400px}header.top h1{margin:0 0 4px;font-size:21px}
29
+ .q{color:var(--muted);margin:0 0 6px}.sum{margin:0;max-width:900px}
30
+ .bar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:8px 24px;position:sticky;top:0;background:var(--bg);z-index:5;border-bottom:1px solid var(--line)}
31
+ .bar button,.bar input{font:inherit;font-size:13px;border:1px solid var(--line);background:var(--panel);color:var(--ink);border-radius:6px;padding:4px 9px;cursor:pointer}
32
+ .bar input{cursor:text;width:180px}.bar button:hover{border-color:var(--accent)}.bar .sep{width:1px;height:20px;background:var(--line);margin:0 4px}
33
+ .bar .hint{color:var(--muted);font-size:12px;margin-left:auto}
34
+ .wrap{display:flex;gap:0;height:calc(100vh - 150px);min-height:420px;padding:10px 24px 0}
35
+ #stage{flex:1;position:relative;overflow:hidden;border:1px solid var(--line);border-radius:10px;background:var(--canvas);touch-action:none;cursor:grab}
36
+ #stage.drag{cursor:grabbing}#stage svg{position:absolute;left:0;top:0;width:100%;height:100%}
37
+ aside#panel{width:340px;margin-left:12px;border:1px solid var(--line);border-radius:10px;background:var(--panel);overflow:auto;padding:14px 16px;display:none}
38
+ aside#panel.open{display:block}aside h3{margin:0 0 2px;font-size:16px}aside .kind{color:var(--muted);font-size:12px;margin-bottom:8px}
39
+ aside .close{float:right;border:0;background:none;color:var(--muted);font-size:18px;cursor:pointer}
40
+ aside pre{background:var(--bg);border:1px solid var(--line);border-radius:6px;padding:8px;overflow:auto;font-size:12px;max-height:260px}
41
+ aside ul{padding-left:18px;margin:4px 0}aside a.jump{color:var(--accent);cursor:pointer;text-decoration:none}
42
+ aside .acts button{font:inherit;font-size:12px;margin:2px 4px 2px 0;border:1px solid var(--line);background:var(--bg);color:var(--ink);border-radius:5px;padding:3px 7px;cursor:pointer}
43
+ .caption{display:none;margin:8px 24px 0;padding:8px 12px;border-left:3px solid var(--hl);background:var(--panel);border-radius:4px}
44
+ .caption.on{display:block}
45
+ section.text{max-width:1100px;padding:10px 24px 40px}section.text h2{font-size:15px;margin:18px 0 4px}
46
+ .tag{font-size:11px;padding:0 5px;border-radius:4px;border:1px solid var(--line);margin-left:4px}.tag.warn{color:var(--warn);border-color:var(--warn)}
47
+ .tag.st-added{color:var(--ok);border-color:var(--ok)}.tag.st-removed{color:var(--bad);border-color:var(--bad)}.tag.st-changed{color:var(--accent);border-color:var(--accent)}
48
+ .muted{color:var(--muted)}.ev{color:var(--muted);font-size:12px}code{font-size:12px}
49
+ .legend{display:flex;flex-wrap:wrap;gap:14px;font-size:12px;color:var(--muted);padding:6px 24px 0}
50
+ .legend i{display:inline-block;width:22px;height:0;border-top:2px solid var(--edge);vertical-align:middle;margin-right:5px}
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)}
57
+ .issues{margin:8px 24px 0;padding:8px 12px;border:1px solid var(--warn);border-radius:6px;color:var(--warn);font-size:13px}
58
+ /* diagram */
59
+ .canvas{fill:var(--canvas)}
60
+ .group rect{fill:var(--group);stroke:var(--line);stroke-width:1}.group.d2 rect{fill:var(--group2)}.group.d3 rect{fill:var(--group)}
61
+ .glabel{font-size:12px;font-weight:600;fill:var(--muted);letter-spacing:.02em}
62
+ .group.st-removed rect{stroke:var(--bad);stroke-dasharray:6 4;fill:none}
63
+ .node{cursor:pointer;outline:none}.node .shape{stroke-width:1.5;fill:var(--panel)}.node .shape.solid{fill:var(--ink);stroke:var(--ink)}
64
+ .node .shape-line{fill:none;stroke-width:1.2}.node .glyph{fill:var(--muted)}
65
+ .node .label{font-size:13px;font-weight:600;fill:var(--ink)}.node .note{font-size:11px;fill:var(--muted)}
66
+ .node .badge{font-size:10px;fill:var(--ok)}.node .badge.warn{fill:var(--warn)}
67
+ .table-head{fill:var(--group2)}.fname{font-size:12px;fill:var(--ink)}.ftype{font-size:11px;fill:var(--muted)}.fkey{font-size:9px;font-weight:700;fill:var(--warn)}
68
+ .c-blue .shape,.c-blue .shape-line{stroke:#3b82f6}.c-violet .shape{stroke:#8b5cf6}.c-green .shape,.c-green .shape-line{stroke:#16a34a}
69
+ .c-teal .shape,.c-teal .shape-line{stroke:#0d9488}.c-amber .shape,.c-amber .shape-line{stroke:#d97706}.c-slate .shape{stroke:#64748b}
70
+ .c-rose .shape{stroke:#e11d48}.c-indigo .shape{stroke:#4f46e5}.k-external .shape{stroke-dasharray:5 3}
71
+ .node.assumed .shape{stroke:var(--warn)!important;stroke-dasharray:6 4}
72
+ .node.st-added .shape{stroke:var(--ok)!important;stroke-width:2.5}.node.st-changed .shape{stroke:var(--accent)!important;stroke-width:2.5}
73
+ .node.st-removed{opacity:.55}.node.st-removed .shape{stroke:var(--bad)!important;stroke-dasharray:6 4}.node.st-removed .label{text-decoration:line-through}
74
+ .edge .line{fill:none;stroke:var(--edge);stroke-width:1.5}.edge .hit{fill:none;stroke:transparent;stroke-width:12;cursor:pointer}
75
+ .edge.s-dashed .line{stroke-dasharray:7 5}.edge.s-dotted .line{stroke-dasharray:2 4}.edge.emph .line{stroke-width:2.6}
76
+ .edge.assumed .line{stroke:var(--warn);stroke-dasharray:6 4}.edge.st-added .line{stroke:var(--ok);stroke-width:2.2}
77
+ .edge.st-removed .line{stroke:var(--bad);stroke-dasharray:6 4;opacity:.7}
78
+ .arrowhead{fill:var(--edge)}.lbg{fill:var(--canvas);opacity:.92}.elabel{font-size:11px;fill:var(--muted)}.card{font-size:11px;font-weight:700;fill:var(--muted)}
79
+ .lifeline{stroke:var(--line);stroke-width:1.2;stroke-dasharray:4 4}.seqno{font-size:10px;fill:var(--muted)}
80
+ .frame rect{fill:none;stroke:var(--muted);stroke-width:1}.frame .ftab{fill:var(--group2);stroke:var(--muted)}.frame text{font-size:11px;fill:var(--ink);font-weight:600}
81
+ svg.focus .node,svg.focus .edge,svg.focus .group{opacity:.18;transition:opacity .15s}
82
+ svg.focus .on,svg.focus .group.on{opacity:1}svg .node.on .shape{stroke-width:2.6}svg .edge.on .line{stroke:var(--hl);stroke-width:2.6}
83
+ svg .node.sel .shape{stroke:var(--hl)!important;stroke-width:3}svg .node:focus-visible .shape{stroke:var(--accent)!important;stroke-width:3}
84
+ @media (prefers-reduced-motion:no-preference){svg .edge.on.flow .line{stroke-dasharray:8 6;animation:flow 1s linear infinite}}
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}
97
+ @media print{.bar,aside,.caption{display:none!important}.wrap{height:auto}#stage{border:0}#stage svg{position:static;height:auto}}
98
+ </style>
99
+ <script type="application/json" id="diagram-data">{"type": "timeline", "slug": "card-payments-failing-3-october", "source_url": null, "story": [], "nodes": [{"id": "deploy", "label": "v2.14 rolled out", "kind": "change", "note": null, "detail": null, "fields": [], "evidence": [{"file": "oncall-notes.md", "start": 5, "end": 5, "quote": "v2.14 rolled out", "verified": true, "snippet": "09:12 deploy: payments v2.14 rolled out to all regions (release bot)"}], "assumed": false, "status": null, "facts": [["when", "03 Oct 09:12"], ["source", "Deploys"]], "group_label": null}, {"id": "climb", "label": "Card errors pass 5%", "kind": "alert", "note": null, "detail": null, "fields": [], "evidence": [{"file": "oncall-notes.md", "start": 6, "end": 6, "quote": "pass 5%", "verified": true, "snippet": "09:19 dashboard: card authorisation errors pass 5% and keep climbing"}], "assumed": false, "status": null, "facts": [["when", "03 Oct 09:19"], ["source", "Monitoring"]], "group_label": null}, {"id": "page", "label": "Pager fires", "kind": "alert", "note": null, "detail": "22 minutes after the climb began: the alert waits for 5% to hold for 20 minutes.", "fields": [], "evidence": [{"file": "oncall-notes.md", "start": 7, "end": 7, "quote": "22 minutes after", "verified": true, "snippet": "09:41 pager: \"card-auth-error-rate\" fires, 22 minutes after the climb began"}], "assumed": false, "status": null, "facts": [["when", "03 Oct 09:41"], ["source", "Monitoring"]], "group_label": null}, {"id": "ack", "label": "Ana acknowledges", "kind": "action", "note": null, "detail": null, "fields": [], "evidence": [{"file": "oncall-notes.md", "start": 8, "end": 8, "quote": "acknowledges", "verified": true, "snippet": "09:44 Ana acknowledges the page and opens the incident"}], "assumed": false, "status": null, "facts": [["when", "03 Oct 09:44"], ["source", "On-call"]], "group_label": null}, {"id": "cause", "label": "Rollout named as the cause", "kind": "action", "note": null, "detail": null, "fields": [], "evidence": [{"file": "oncall-notes.md", "start": 9, "end": 9, "quote": "started with the 2.14 rollout", "verified": true, "snippet": "09:51 Ana: errors started with the 2.14 rollout, nothing else changed"}], "assumed": false, "status": null, "facts": [["when", "03 Oct 09:51"], ["source", "On-call"]], "group_label": null}, {"id": "rollback", "label": "Rollback to v2.13", "kind": "change", "note": null, "detail": null, "fields": [], "evidence": [{"file": "oncall-notes.md", "start": 10, "end": 11, "quote": "rollback finished", "verified": true, "snippet": "09:58 rollback to v2.13 started\n10:09 rollback finished in all regions"}], "assumed": false, "status": null, "facts": [["when", "03 Oct 09:58"], ["source", "Deploys"]], "group_label": null}, {"id": "normal", "label": "Errors back under 0.5%", "kind": "milestone", "note": null, "detail": null, "fields": [], "evidence": [{"file": "oncall-notes.md", "start": 12, "end": 12, "quote": "back under 0.5%", "verified": true, "snippet": "10:11 dashboard: card errors back under 0.5%"}], "assumed": false, "status": null, "facts": [["when", "03 Oct 10:11"], ["source", "Monitoring"]], "group_label": null}, {"id": "status", "label": "Status page: resolved", "kind": "action", "note": null, "detail": null, "fields": [], "evidence": [{"file": "oncall-notes.md", "start": 13, "end": 13, "quote": "resolved", "verified": true, "snippet": "10:30 status page updated: resolved"}], "assumed": false, "status": null, "facts": [["when", "03 Oct 10:30"], ["source", "On-call"]], "group_label": null}, {"id": "pm", "label": "Postmortem", "kind": "milestone", "note": null, "detail": "Action: alert on the rise in the error rate, not on a fixed 5% for 20 minutes.", "fields": [], "evidence": [{"file": "oncall-notes.md", "start": 14, "end": 14, "quote": "postmortem held", "verified": true, "snippet": "6 Oct 14:00 postmortem held; action: alert on the error rate's rise, not a fixed 5% for 20 minutes"}], "assumed": false, "status": null, "facts": [["when", "06 Oct 14:00"], ["source", "On-call"]], "group_label": null}], "edges": [{"id": "e1", "from": "deploy", "to": "climb", "label": "caused", "detail": null, "evidence": [{"file": "oncall-notes.md", "start": 9, "end": 9, "quote": "errors started with the 2.14 rollout", "verified": true, "snippet": "09:51 Ana: errors started with the 2.14 rollout, nothing else changed"}], "assumed": false, "status": null}, {"id": "e2", "from": "climb", "to": "page", "label": "detected 22 min later", "detail": null, "evidence": [{"file": "oncall-notes.md", "start": 7, "end": 7, "verified": true, "snippet": "09:41 pager: \"card-auth-error-rate\" fires, 22 minutes after the climb began"}], "assumed": false, "status": null}], "mermaid": "timeline\n title Card payments failing, 3 October\n section Deploys\n 03 Oct 09.12 : v2.14 rolled out\n 03 Oct 09.58 : Rollback to v2.13\n section Monitoring\n 03 Oct 09.19 : Card errors pass 5%\n 03 Oct 09.41 : Pager fires\n 03 Oct 10.11 : Errors back under 0.5%\n section On-call\n 03 Oct 09.44 : Ana acknowledges\n 03 Oct 09.51 : Rollout named as the cause\n 03 Oct 10.30 : Status page - resolved\n 06 Oct 14.00 : Postmortem\n", "words": {"out": "Led to", "in": "Followed from", "reach_out": "What followed", "reach_in": "What led to it"}, "auto_steps": [{"text": "03 Oct 09:12 — v2.14 rolled out", "nodes": ["deploy"]}, {"text": "03 Oct 09:19 — Card errors pass 5%", "nodes": ["climb"]}, {"text": "03 Oct 09:41 — Pager fires", "nodes": ["page"]}, {"text": "03 Oct 09:44 — Ana acknowledges", "nodes": ["ack"]}, {"text": "03 Oct 09:51 — Rollout named as the cause", "nodes": ["cause"]}, {"text": "03 Oct 09:58 — Rollback to v2.13", "nodes": ["rollback"]}, {"text": "03 Oct 10:11 — Errors back under 0.5%", "nodes": ["normal"]}, {"text": "03 Oct 10:30 — Status page: resolved", "nodes": ["status"]}, {"text": "06 Oct 14:00 — Postmortem", "nodes": ["pm"]}]}</script>
100
+ <script>
101
+ (function(){
102
+ var DATA=JSON.parse(document.getElementById('diagram-data').textContent);var WD=DATA.words;
103
+ var svg=document.getElementById('map'),stage=document.getElementById('stage'),panel=document.getElementById('panel');
104
+ var root=document.documentElement;
105
+ var pref=localStorage.getItem('idg-theme')||root.getAttribute('data-theme-default');
106
+ if(pref==='auto'||!pref){pref=window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}
107
+ root.setAttribute('data-theme',pref);
108
+ var W=+svg.getAttribute('width'),H=+svg.getAttribute('height');svg.removeAttribute('width');svg.removeAttribute('height');
109
+ var vb={x:0,y:0,w:W,h:H};
110
+ function apply(){svg.setAttribute('viewBox',vb.x+' '+vb.y+' '+vb.w+' '+vb.h)}
111
+ function fit(){var r=stage.getBoundingClientRect();var s=Math.max(W/r.width,H/r.height,0.6);vb.w=r.width*s;vb.h=r.height*s;vb.x=(W-vb.w)/2;vb.y=(H-vb.h)/2;apply()}
112
+ function zoom(f,cx,cy){var r=stage.getBoundingClientRect();if(cx==null){cx=r.width/2;cy=r.height/2}
113
+ var px=vb.x+cx/r.width*vb.w,py=vb.y+cy/r.height*vb.h;var nw=Math.min(Math.max(vb.w*f,120),W*6);var k=nw/vb.w;
114
+ vb.x=px-(px-vb.x)*k;vb.y=py-(py-vb.y)*k;vb.w=nw;vb.h=vb.h*k;apply()}
115
+ stage.addEventListener('wheel',function(e){e.preventDefault();var r=stage.getBoundingClientRect();zoom(e.deltaY>0?1.12:1/1.12,e.clientX-r.left,e.clientY-r.top)},{passive:false});
116
+ var drag=null;
117
+ stage.addEventListener('pointerdown',function(e){if(e.target.closest('.node')||e.target.closest('.edge'))return;drag={x:e.clientX,y:e.clientY,vx:vb.x,vy:vb.y};stage.classList.add('drag');stage.setPointerCapture(e.pointerId)});
118
+ stage.addEventListener('pointermove',function(e){if(!drag)return;var r=stage.getBoundingClientRect();vb.x=drag.vx-(e.clientX-drag.x)*vb.w/r.width;vb.y=drag.vy-(e.clientY-drag.y)*vb.h/r.height;apply()});
119
+ stage.addEventListener('pointerup',function(){drag=null;stage.classList.remove('drag')});
120
+ // Lookups are Maps, never plain objects: ids come from the spec and the URL, and a key such as
121
+ // __proto__ must stay a key.
122
+ var nodes=new Map(),edges=new Map(),outE=new Map(),inE=new Map();
123
+ DATA.nodes.forEach(function(n){nodes.set(n.id,n);outE.set(n.id,[]);inE.set(n.id,[])});
124
+ DATA.edges.forEach(function(e){edges.set(e.id,e);if(outE.has(e.from))outE.get(e.from).push(e.id);if(inE.has(e.to))inE.get(e.to).push(e.id)});
125
+ function el(kind,id){return document.getElementById(kind+'-'+id)}
126
+ function clear(){svg.classList.remove('focus');svg.querySelectorAll('.on,.sel,.flow').forEach(function(x){x.classList.remove('on','sel','flow')})}
127
+ function light(ns,es,flow){clear();svg.classList.add('focus');ns.forEach(function(i){var x=el('n',i);x&&x.classList.add('on')});
128
+ es.forEach(function(i){var x=el('e',i);if(x){x.classList.add('on');flow&&x.classList.add('flow')}})}
129
+ function reach(id,dir){if(!nodes.has(id))return {n:[],e:[]};var seen=new Set([id]),es=[],q=[id];while(q.length){var u=q.shift();(dir>0?outE.get(u):inE.get(u)).forEach(function(eid){var e=edges.get(eid);es.push(eid);var v=dir>0?e.to:e.from;if(!seen.has(v)){seen.add(v);q.push(v)}})}
130
+ return {n:Array.from(seen),e:es}}
131
+ function path(a,b,directed){if(!nodes.has(a)||!nodes.has(b))return null;var prev=new Map([[a,null]]),q=[a];while(q.length){var u=q.shift();if(u===b)break;
132
+ var nb=outE.get(u).map(function(i){return [edges.get(i).to,i]});if(!directed)nb=nb.concat(inE.get(u).map(function(i){return [edges.get(i).from,i]}));
133
+ nb.forEach(function(p){if(!prev.has(p[0])){prev.set(p[0],[u,p[1]]);q.push(p[0])}})}
134
+ if(!prev.has(b))return null;var ns=[b],es=[],c=b;while(prev.get(c)){es.push(prev.get(c)[1]);c=prev.get(c)[0];ns.push(c)}return {n:ns.reverse(),e:es.reverse()}}
135
+ function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]})}
136
+ function evHtml(list){if(!list||!list.length)return '<p class="muted">No evidence cited.</p>';
137
+ return '<ul>'+list.map(function(e){var t=e.said!=null?(esc(e.source||'user')+': “'+esc(e.said)+'”'):esc(e.file+(e.start?':'+e.start+(e.end!==e.start?'-'+e.end:''):''));
138
+ if(e.file&&DATA.source_url){t='<a href="'+esc(DATA.source_url.replace(/\/?$/,'/')+e.file+(e.start?'#L'+e.start+(e.end!==e.start?'-L'+e.end:''):''))+'" target="_blank" rel="noopener">'+t+'</a>'}
139
+ var v=e.verified===true?' <span class="tag" style="color:var(--ok)">checked</span>':(e.verified===null&&e.file?' <span class="tag">not checked</span>':'');
140
+ return '<li>'+t+v+(e.snippet?'<pre>'+esc(e.snippet)+'</pre>':'')+'</li>'}).join('')+'</ul>'}
141
+ var selected=null;
142
+ function show(kind,id){var x=kind==='n'?nodes.get(id):edges.get(id);if(!x)return;var h='<button class="close" aria-label="Close">×</button>';
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>';
144
+ if(x.note)h+='<p>'+esc(x.note)+'</p>';if(x.detail)h+='<p>'+esc(x.detail)+'</p>';
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>';
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>';
148
+ h+='<p class="muted" style="font-size:12px">Shift-click another part to see the path between them.</p>';
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('');
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('');
151
+ if(o)h+='<b>'+esc(WD.out)+'</b><ul>'+o+'</ul>';if(n)h+='<b>'+esc(WD.in)+'</b><ul>'+n+'</ul>';
152
+ h+='<b>Evidence</b>'+evHtml(x.evidence);
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');
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>';
155
+ if(x.detail)h+='<p>'+esc(x.detail)+'</p>';h+='<b>Evidence</b>'+evHtml(x.evidence);light([x.from,x.to],[id],true)}
156
+ panel.innerHTML=h;panel.classList.add('open');selected=kind==='n'?id:null;
157
+ panel.querySelector('.close').onclick=function(){panel.classList.remove('open');clear();selected=null};
158
+ panel.querySelectorAll('.jump').forEach(function(a){a.onclick=function(){show('n',a.getAttribute('data-n'))}});
159
+ panel.querySelectorAll('[data-act]').forEach(function(b){b.onclick=function(){var a=b.getAttribute('data-act');
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')}})}
161
+ svg.addEventListener('click',function(e){var g=e.target.closest('.node');if(g){var id=g.getAttribute('data-id');
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}
163
+ var ed=e.target.closest('.edge');if(ed){show('e',ed.getAttribute('data-id'));return}});
164
+ svg.addEventListener('keydown',function(e){var g=e.target.closest&&e.target.closest('.node');if(g&&(e.key==='Enter'||e.key===' ')){e.preventDefault();show('n',g.getAttribute('data-id'))}});
165
+ document.addEventListener('keydown',function(e){if(e.key==='Escape'){panel.classList.remove('open');clear();selected=null}
166
+ if(e.target.tagName==='INPUT')return;if(e.key==='ArrowRight'&&tour)step(1);if(e.key==='ArrowLeft'&&tour)step(-1)});
167
+ var q=document.getElementById('find');q.addEventListener('input',function(){var v=q.value.trim().toLowerCase();if(!v){clear();return}
168
+ var ns=DATA.nodes.filter(function(n){return (n.label+' '+(n.note||'')+' '+(n.fields||[]).map(function(f){return f.name}).join(' ')).toLowerCase().indexOf(v)>=0}).map(function(n){return n.id});light(ns,[])});
169
+ document.getElementById('zin').onclick=function(){zoom(1/1.25)};document.getElementById('zout').onclick=function(){zoom(1.25)};document.getElementById('zfit').onclick=fit;
170
+ document.getElementById('theme').onclick=function(){var t=root.getAttribute('data-theme')==='dark'?'light':'dark';root.setAttribute('data-theme',t);try{localStorage.setItem('idg-theme',t)}catch(e){}};
171
+ function download(name,type,data){var a=document.createElement('a');a.href=URL.createObjectURL(new Blob([data],{type:type}));a.download=name;document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(a.href);a.remove()},500)}
172
+ function standalone(){var c=svg.cloneNode(true);c.setAttribute('viewBox','0 0 '+W+' '+H);c.setAttribute('width',W);c.setAttribute('height',H);c.classList.remove('focus');
173
+ c.querySelectorAll('.on,.sel,.flow').forEach(function(x){x.classList.remove('on','sel','flow')});
174
+ var cs=getComputedStyle(root),vars=['--bg','--panel','--ink','--muted','--line','--edge','--accent','--warn','--ok','--bad','--hl','--canvas','--group','--group2'].map(function(v){return v+':'+cs.getPropertyValue(v)}).join(';');
175
+ var st=document.createElementNS('http://www.w3.org/2000/svg','style');st.textContent='svg{'+vars+';font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif}'+document.getElementById('diagram-css').textContent.split('/* diagram */')[1];
176
+ c.insertBefore(st,c.firstChild);return new XMLSerializer().serializeToString(c)}
177
+ var base=(DATA.slug||'diagram');window.interactiveDiagram={svg:standalone,mermaid:function(){return DATA.mermaid}};
178
+ document.getElementById('xsvg').onclick=function(){download(base+'.svg','image/svg+xml',standalone())};
179
+ document.getElementById('xpng').onclick=function(){var img=new Image();img.onload=function(){var k=2,cv=document.createElement('canvas');cv.width=W*k;cv.height=H*k;var ctx=cv.getContext('2d');ctx.scale(k,k);ctx.drawImage(img,0,0);cv.toBlob(function(b){download(base+'.png','image/png',b)})};
180
+ img.src='data:image/svg+xml;charset=utf-8,'+encodeURIComponent(standalone())};
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)};
182
+ var tour=null,cap=document.getElementById('caption');
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]}}):[]);
184
+ var tb=document.getElementById('tour');if(!steps.length){tb.style.display='none'}
185
+ function step(d){tour.i=Math.max(0,Math.min(steps.length-1,tour.i+d));var s=steps[tour.i];
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);
187
+ cap.innerHTML='<b>Step '+(tour.i+1)+' of '+steps.length+'</b> — '+esc(s.text||'')+' <span class="muted">(← → to move, Esc to stop)</span>';cap.classList.add('on')}
188
+ tb.onclick=function(){if(tour){tour=null;cap.classList.remove('on');clear();tb.textContent='Walk through';return}tour={i:-1};tb.textContent='Stop';step(1)};
189
+ document.getElementById('prev').onclick=function(){if(tour)step(-1)};document.getElementById('next').onclick=function(){if(!tour){tb.onclick()}else step(1)};
190
+ if(!steps.length){document.getElementById('prev').style.display='none';document.getElementById('next').style.display='none'}
191
+ function size(){var wr=document.querySelector('.wrap');var avail=Math.max(window.innerHeight-150,360);var r=stage.getBoundingClientRect();
192
+ var s=Math.max(W/Math.max(r.width,1),0.6);wr.style.height=Math.min(avail,Math.max(H/s+24,300))+'px'}
193
+ size();fit();window.addEventListener('resize',function(){size();fit()});
194
+ function hash(){var h=decodeURIComponent(location.hash.slice(1));if(!h)return;var kv=new Map();h.split('&').forEach(function(p){var i=p.indexOf('=');if(i>0)kv.set(p.slice(0,i),p.slice(i+1))});
195
+ if(kv.get('theme'))root.setAttribute('data-theme',kv.get('theme'));
196
+ if(kv.get('n')&&nodes.get(kv.get('n')))show('n',kv.get('n'));if(kv.get('e')&&edges.get(kv.get('e')))show('e',kv.get('e'));
197
+ if(kv.get('path')){var ab=kv.get('path').split(',');var p=path(ab[0],ab[1],true)||path(ab[0],ab[1],false);if(p)light(p.n,p.e,true)}
198
+ if(kv.get('reach')){var ar=kv.get('reach').split(',');var r=reach(ar[0],ar[1]==='up'?-1:1);light(r.n,r.e,true)}
199
+ if(kv.get('step')&&steps.length){tour={i:-1};tb.textContent='Stop';step(+kv.get('step'))}}
200
+ hash();window.addEventListener('hashchange',hash);
201
+ })();
202
+ </script>
203
+ </body></html>
@@ -0,0 +1,21 @@
1
+ {"type": "timeline",
2
+ "title": "Card payments failing, 3 October",
3
+ "question": "What happened, and how long did it take anyone to notice?",
4
+ "summary": "Errors climbed seven minutes after the 2.14 rollout, but the page fired 22 minutes later because the alert waits for a fixed 5% to hold. From the page, rollback took 28 minutes.",
5
+ "groups": [{"id": "deploys", "label": "Deploys"}, {"id": "monitoring", "label": "Monitoring"}, {"id": "oncall", "label": "On-call"}],
6
+ "events": [
7
+ {"id": "deploy", "label": "v2.14 rolled out", "at": "2026-10-03T09:12", "lane": "deploys", "kind": "change", "evidence": ["oncall-notes.md:5#v2.14 rolled out"]},
8
+ {"id": "climb", "label": "Card errors pass 5%", "at": "2026-10-03T09:19", "lane": "monitoring", "kind": "alert", "evidence": ["oncall-notes.md:6#pass 5%"]},
9
+ {"id": "page", "label": "Pager fires", "at": "2026-10-03T09:41", "lane": "monitoring", "kind": "alert", "detail": "22 minutes after the climb began: the alert waits for 5% to hold for 20 minutes.", "evidence": ["oncall-notes.md:7#22 minutes after"]},
10
+ {"id": "ack", "label": "Ana acknowledges", "at": "2026-10-03T09:44", "lane": "oncall", "kind": "action", "evidence": ["oncall-notes.md:8#acknowledges"]},
11
+ {"id": "cause", "label": "Rollout named as the cause", "at": "2026-10-03T09:51", "lane": "oncall", "kind": "action", "evidence": ["oncall-notes.md:9#started with the 2.14 rollout"]},
12
+ {"id": "rollback", "label": "Rollback to v2.13", "at": "2026-10-03T09:58", "end": "2026-10-03T10:09", "lane": "deploys", "kind": "change", "evidence": ["oncall-notes.md:10-11#rollback finished"]},
13
+ {"id": "normal", "label": "Errors back under 0.5%", "at": "2026-10-03T10:11", "lane": "monitoring", "kind": "milestone", "evidence": ["oncall-notes.md:12#back under 0.5%"]},
14
+ {"id": "status", "label": "Status page: resolved", "at": "2026-10-03T10:30", "lane": "oncall", "kind": "action", "evidence": ["oncall-notes.md:13#resolved"]},
15
+ {"id": "pm", "label": "Postmortem", "at": "2026-10-06T14:00", "lane": "oncall", "kind": "milestone", "detail": "Action: alert on the rise in the error rate, not on a fixed 5% for 20 minutes.", "evidence": ["oncall-notes.md:14#postmortem held"]}
16
+ ],
17
+ "edges": [
18
+ {"from": "deploy", "to": "climb", "label": "caused", "evidence": ["oncall-notes.md:9#errors started with the 2.14 rollout"]},
19
+ {"from": "climb", "to": "page", "label": "detected 22 min later", "evidence": ["oncall-notes.md:7"]}
20
+ ],
21
+ "notes": ["Customer impact (how many payments failed) is not in the notes, so it is not on the timeline."]}
@@ -0,0 +1,41 @@
1
+ """POST /checkout: price the cart, take payment, record the order, hand off fulfilment."""
2
+ import json
3
+ import os
4
+
5
+ import pika
6
+ import psycopg
7
+ import redis
8
+ import stripe
9
+ from fastapi import FastAPI, HTTPException
10
+
11
+ app = FastAPI()
12
+ db = psycopg.connect(os.environ["DATABASE_URL"])
13
+ prices = redis.Redis.from_url(os.environ["REDIS_URL"])
14
+ broker = pika.BlockingConnection(pika.URLParameters(os.environ["BROKER_URL"])).channel()
15
+
16
+
17
+ def price_cart(cart):
18
+ total = 0
19
+ for line in cart["lines"]:
20
+ cached = prices.get(f"price:{line['sku']}")
21
+ if cached is None:
22
+ row = db.execute("select price from products where sku = %s", (line["sku"],)).fetchone()
23
+ cached = row[0]
24
+ prices.set(f"price:{line['sku']}", cached, ex=300)
25
+ total += int(cached) * line["qty"]
26
+ return total
27
+
28
+
29
+ @app.post("/checkout")
30
+ def checkout(cart: dict):
31
+ total = price_cart(cart)
32
+ intent = stripe.PaymentIntent.create(amount=total, currency="gbp", confirm=True,
33
+ payment_method=cart["payment_method"])
34
+ if intent.status != "succeeded":
35
+ raise HTTPException(402, "payment declined")
36
+ order_id = db.execute("insert into orders (email, total, payment_id) values (%s, %s, %s) returning id",
37
+ (cart["email"], total, intent.id)).fetchone()[0]
38
+ db.commit()
39
+ broker.basic_publish(exchange="", routing_key="fulfilment",
40
+ body=json.dumps({"order_id": order_id}))
41
+ return {"order_id": order_id}
@@ -0,0 +1,20 @@
1
+ services:
2
+ api:
3
+ build: ./api
4
+ environment:
5
+ DATABASE_URL: postgresql://shop@db/shop
6
+ REDIS_URL: redis://cache:6379/0
7
+ BROKER_URL: amqp://broker
8
+ depends_on: [db, cache, broker]
9
+ worker:
10
+ build: ./worker
11
+ environment:
12
+ DATABASE_URL: postgresql://shop@db/shop
13
+ BROKER_URL: amqp://broker
14
+ depends_on: [db, broker]
15
+ db:
16
+ image: postgres:16
17
+ cache:
18
+ image: redis:7
19
+ broker:
20
+ image: rabbitmq:3
@@ -0,0 +1,23 @@
1
+ """Consumes the fulfilment queue and books a courier for each paid order."""
2
+ import json
3
+ import os
4
+
5
+ import pika
6
+ import psycopg
7
+ import requests
8
+
9
+ db = psycopg.connect(os.environ["DATABASE_URL"])
10
+ channel = pika.BlockingConnection(pika.URLParameters(os.environ["BROKER_URL"])).channel()
11
+
12
+
13
+ def on_message(ch, method, properties, body):
14
+ order_id = json.loads(body)["order_id"]
15
+ address = db.execute("select address from orders where id = %s", (order_id,)).fetchone()[0]
16
+ booking = requests.post("https://courier.example/v2/bookings", json={"ref": order_id, "to": address}, timeout=10)
17
+ db.execute("update orders set status = 'booked', courier_ref = %s where id = %s", (booking.json()["ref"], order_id))
18
+ db.commit()
19
+ ch.basic_ack(method.delivery_tag)
20
+
21
+
22
+ channel.basic_consume(queue="fulfilment", on_message_callback=on_message)
23
+ channel.start_consuming()
@@ -0,0 +1,14 @@
1
+ # Incident 2026-10-03: card payments failing
2
+
3
+ Notes kept in the on-call channel, times in UTC.
4
+
5
+ 09:12 deploy: payments v2.14 rolled out to all regions (release bot)
6
+ 09:19 dashboard: card authorisation errors pass 5% and keep climbing
7
+ 09:41 pager: "card-auth-error-rate" fires, 22 minutes after the climb began
8
+ 09:44 Ana acknowledges the page and opens the incident
9
+ 09:51 Ana: errors started with the 2.14 rollout, nothing else changed
10
+ 09:58 rollback to v2.13 started
11
+ 10:09 rollback finished in all regions
12
+ 10:11 dashboard: card errors back under 0.5%
13
+ 10:30 status page updated: resolved
14
+ 6 Oct 14:00 postmortem held; action: alert on the error rate's rise, not a fixed 5% for 20 minutes
@@ -0,0 +1,9 @@
1
+ {"span": "POST /checkout", "id": "a1", "parent": null, "service": "api", "start_ms": 0, "end_ms": 1840}
2
+ {"span": "verify session", "id": "a2", "parent": "a1", "service": "api", "start_ms": 4, "end_ms": 38}
3
+ {"span": "price cart", "id": "a3", "parent": "a1", "service": "api", "start_ms": 40, "end_ms": 420}
4
+ {"span": "redis MGET price:*", "id": "a4", "parent": "a3", "service": "redis", "start_ms": 42, "end_ms": 61}
5
+ {"span": "select price from products (cache miss)", "id": "a5", "parent": "a3", "service": "postgres", "start_ms": 64, "end_ms": 410}
6
+ {"span": "stripe.PaymentIntent.create", "id": "a6", "parent": "a1", "service": "stripe", "start_ms": 430, "end_ms": 1620}
7
+ {"span": "TLS handshake api.stripe.com", "id": "a7", "parent": "a6", "service": "stripe", "start_ms": 431, "end_ms": 520}
8
+ {"span": "insert into orders", "id": "a8", "parent": "a1", "service": "postgres", "start_ms": 1625, "end_ms": 1790}
9
+ {"span": "publish fulfilment", "id": "a9", "parent": "a1", "service": "rabbitmq", "start_ms": 1792, "end_ms": 1836}